imap-processing 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imap-processing might be problematic. Click here for more details.
- imap_processing/__init__.py +1 -0
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/ccsds_data.py +1 -2
- imap_processing/ccsds/excel_to_xtce.py +1 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -12
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +569 -0
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +5 -5
- imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +20 -1
- 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_mag_global_cdf_attrs.yaml +15 -0
- imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
- imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +16 -0
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +178 -5
- imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -19
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +8 -48
- imap_processing/cdf/utils.py +41 -33
- imap_processing/cli.py +463 -234
- imap_processing/codice/codice_l1a.py +260 -47
- imap_processing/codice/codice_l1b.py +51 -152
- imap_processing/codice/constants.py +38 -1
- imap_processing/ena_maps/ena_maps.py +658 -65
- imap_processing/ena_maps/utils/coordinates.py +1 -1
- imap_processing/ena_maps/utils/spatial_utils.py +10 -5
- 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 +31 -12
- imap_processing/hi/l1b/hi_l1b.py +80 -43
- imap_processing/hi/l1c/hi_l1c.py +12 -16
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
- imap_processing/hit/hit_utils.py +93 -35
- imap_processing/hit/l0/decom_hit.py +3 -1
- imap_processing/hit/l1a/hit_l1a.py +30 -25
- imap_processing/hit/l1b/constants.py +6 -2
- imap_processing/hit/l1b/hit_l1b.py +279 -318
- imap_processing/hit/l2/constants.py +37 -0
- imap_processing/hit/l2/hit_l2.py +373 -264
- imap_processing/ialirt/l0/parse_mag.py +138 -10
- imap_processing/ialirt/l0/process_swapi.py +69 -0
- imap_processing/ialirt/l0/process_swe.py +318 -22
- imap_processing/ialirt/packet_definitions/ialirt.xml +216 -212
- 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_swapi.xml +14 -14
- imap_processing/ialirt/utils/grouping.py +1 -1
- imap_processing/idex/idex_constants.py +9 -1
- imap_processing/idex/idex_l0.py +22 -8
- imap_processing/idex/idex_l1a.py +75 -44
- imap_processing/idex/idex_l1b.py +9 -8
- imap_processing/idex/idex_l2a.py +79 -45
- imap_processing/idex/idex_l2b.py +120 -0
- imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -39
- imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
- imap_processing/lo/l0/lo_science.py +1 -2
- imap_processing/lo/l1a/lo_l1a.py +1 -4
- imap_processing/lo/l1b/lo_l1b.py +527 -6
- imap_processing/lo/l1b/tof_conversions.py +11 -0
- imap_processing/lo/l1c/lo_l1c.py +1 -4
- imap_processing/mag/constants.py +43 -0
- imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
- imap_processing/mag/l1a/mag_l1a.py +2 -9
- imap_processing/mag/l1a/mag_l1a_data.py +10 -10
- imap_processing/mag/l1b/mag_l1b.py +84 -17
- imap_processing/mag/l1c/interpolation_methods.py +180 -3
- imap_processing/mag/l1c/mag_l1c.py +236 -70
- imap_processing/mag/l2/mag_l2.py +140 -0
- imap_processing/mag/l2/mag_l2_data.py +288 -0
- imap_processing/spacecraft/quaternions.py +1 -3
- imap_processing/spice/geometry.py +3 -3
- imap_processing/spice/kernels.py +0 -276
- imap_processing/spice/pointing_frame.py +257 -0
- imap_processing/spice/repoint.py +48 -19
- imap_processing/spice/spin.py +38 -33
- imap_processing/spice/time.py +24 -0
- imap_processing/swapi/l1/swapi_l1.py +16 -12
- imap_processing/swapi/l2/swapi_l2.py +116 -4
- imap_processing/swapi/swapi_utils.py +32 -0
- imap_processing/swe/l1a/swe_l1a.py +2 -9
- imap_processing/swe/l1a/swe_science.py +8 -11
- imap_processing/swe/l1b/swe_l1b.py +898 -23
- imap_processing/swe/l2/swe_l2.py +21 -77
- imap_processing/swe/utils/swe_constants.py +1 -0
- imap_processing/tests/ccsds/test_excel_to_xtce.py +1 -1
- 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-pha_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/test_codice_l1a.py +20 -11
- imap_processing/tests/codice/test_codice_l1b.py +6 -7
- imap_processing/tests/conftest.py +78 -22
- imap_processing/tests/ena_maps/test_ena_maps.py +462 -33
- imap_processing/tests/ena_maps/test_spatial_utils.py +1 -1
- 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/test_hi_l1b.py +10 -12
- imap_processing/tests/hi/test_hi_l1c.py +27 -24
- imap_processing/tests/hi/test_l1a.py +7 -9
- imap_processing/tests/hi/test_science_direct_event.py +2 -2
- imap_processing/tests/hit/helpers/l1_validation.py +44 -43
- imap_processing/tests/hit/test_decom_hit.py +1 -1
- imap_processing/tests/hit/test_hit_l1a.py +9 -9
- imap_processing/tests/hit/test_hit_l1b.py +172 -217
- imap_processing/tests/hit/test_hit_l2.py +380 -118
- imap_processing/tests/hit/test_hit_utils.py +122 -55
- imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
- imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
- imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
- imap_processing/tests/ialirt/unit/test_grouping.py +2 -2
- imap_processing/tests/ialirt/unit/test_parse_mag.py +71 -16
- 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 +115 -7
- imap_processing/tests/idex/conftest.py +72 -7
- 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_idex_l0.py +33 -11
- imap_processing/tests/idex/test_idex_l1a.py +50 -23
- imap_processing/tests/idex/test_idex_l1b.py +104 -25
- imap_processing/tests/idex/test_idex_l2a.py +48 -32
- imap_processing/tests/idex/test_idex_l2b.py +93 -0
- imap_processing/tests/lo/test_lo_l1a.py +3 -3
- imap_processing/tests/lo/test_lo_l1b.py +371 -6
- imap_processing/tests/lo/test_lo_l1c.py +1 -1
- imap_processing/tests/lo/test_lo_science.py +6 -7
- imap_processing/tests/lo/test_star_sensor.py +1 -1
- imap_processing/tests/mag/conftest.py +58 -9
- imap_processing/tests/mag/test_mag_decom.py +4 -3
- imap_processing/tests/mag/test_mag_l1a.py +13 -7
- imap_processing/tests/mag/test_mag_l1b.py +9 -9
- imap_processing/tests/mag/test_mag_l1c.py +151 -47
- imap_processing/tests/mag/test_mag_l2.py +130 -0
- imap_processing/tests/mag/test_mag_validation.py +144 -7
- 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_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/test_quaternions.py +1 -1
- imap_processing/tests/spice/test_data/fake_repoint_data.csv +4 -4
- imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
- imap_processing/tests/spice/test_geometry.py +3 -3
- 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 +20 -10
- 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 +7 -9
- imap_processing/tests/swapi/test_swapi_l2.py +180 -8
- 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 +6 -6
- 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 +82 -102
- imap_processing/tests/test_cli.py +171 -88
- imap_processing/tests/test_utils.py +2 -1
- imap_processing/tests/ultra/data/mock_data.py +49 -21
- imap_processing/tests/ultra/unit/conftest.py +53 -70
- imap_processing/tests/ultra/unit/test_badtimes.py +2 -4
- imap_processing/tests/ultra/unit/test_cullingmask.py +4 -6
- imap_processing/tests/ultra/unit/test_de.py +3 -10
- imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
- imap_processing/tests/ultra/unit/test_decom_apid_881.py +15 -16
- 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 +3 -4
- imap_processing/tests/ultra/unit/test_ultra_l1a.py +84 -307
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +30 -12
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
- imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +4 -1
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +163 -29
- imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +32 -43
- 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 +21 -34
- 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 -243
- imap_processing/ultra/l1b/badtimes.py +1 -4
- imap_processing/ultra/l1b/cullingmask.py +2 -6
- imap_processing/ultra/l1b/de.py +62 -47
- imap_processing/ultra/l1b/extendedspin.py +8 -4
- imap_processing/ultra/l1b/lookup_utils.py +72 -9
- imap_processing/ultra/l1b/ultra_l1b.py +3 -8
- imap_processing/ultra/l1b/ultra_l1b_culling.py +4 -4
- imap_processing/ultra/l1b/ultra_l1b_extended.py +236 -78
- imap_processing/ultra/l1c/histogram.py +2 -6
- imap_processing/ultra/l1c/spacecraft_pset.py +2 -4
- imap_processing/ultra/l1c/ultra_l1c.py +1 -5
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +107 -60
- 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 +13 -12
- imap_processing/utils.py +1 -1
- {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +3 -2
- {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/RECORD +264 -225
- imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
- imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +0 -6
- imap_processing/mag/l1b/__init__.py +0 -0
- imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
- imap_processing/swe/l1b/swe_l1b_science.py +0 -699
- imap_processing/tests/swe/test_swe_l1b_science.py +0 -103
- imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
- imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
- /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/461971383-404.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971384-405.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971385-406.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971386-407.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971387-408.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971388-409.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971389-410.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971390-411.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/461971391-412.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/tests/ialirt/{test_data → data}/l0/sample_decoded_i-alirt_data.csv +0 -0
- /imap_processing/tests/mag/validation/{imap_calibration_mag_20240229_v01.cdf → calibration/imap_mag_l1b-calibration_20240229_v001.cdf} +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-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Data structures for MAG L2 and L1D processing."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import InitVar, dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import xarray as xr
|
|
8
|
+
|
|
9
|
+
from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
|
|
10
|
+
from imap_processing.mag.constants import DataMode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValidFrames(Enum):
|
|
14
|
+
"""SPICE reference frames for output."""
|
|
15
|
+
|
|
16
|
+
dsrf = "dsrf"
|
|
17
|
+
srf = "srf"
|
|
18
|
+
rtn = "rtn"
|
|
19
|
+
gse = "gse"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class MagL2:
|
|
24
|
+
"""
|
|
25
|
+
Dataclass for MAG L2 data.
|
|
26
|
+
|
|
27
|
+
Since L2 and L1D should have the same structure, this can be used for either level.
|
|
28
|
+
|
|
29
|
+
Some of the methods are also static, so they can be used in i-ALiRT processing.
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
vectors: np.ndarray
|
|
34
|
+
Magnetic field vectors of size (n, 3) where n is the number of vectors.
|
|
35
|
+
Describes (x, y, z) components of the magnetic field.
|
|
36
|
+
epoch: np.ndarray
|
|
37
|
+
Time of each vector in J2000 seconds. Should be of length n.
|
|
38
|
+
range: np.ndarray
|
|
39
|
+
Range of each vector. Should be of length n.
|
|
40
|
+
global_attributes: dict
|
|
41
|
+
Any global attributes we want to carry forward into the output CDF file.
|
|
42
|
+
quality_flags: np.ndarray
|
|
43
|
+
Quality flags for each vector. Should be of length n.
|
|
44
|
+
quality_bitmask: np.ndarray
|
|
45
|
+
Quality bitmask for each vector. Should be of length n. Copied from offset
|
|
46
|
+
file in L2, marked as good always in L1D.
|
|
47
|
+
magnitude: np.ndarray
|
|
48
|
+
Magnitude of each vector. Should be of length n. Calculated from L2 vectors.
|
|
49
|
+
is_l1d: bool
|
|
50
|
+
Flag to indicate if the data is L1D. Defaults to False.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
vectors: np.ndarray
|
|
54
|
+
epoch: np.ndarray
|
|
55
|
+
range: np.ndarray
|
|
56
|
+
global_attributes: dict
|
|
57
|
+
quality_flags: np.ndarray
|
|
58
|
+
quality_bitmask: np.ndarray
|
|
59
|
+
data_mode: DataMode
|
|
60
|
+
magnitude: np.ndarray = field(init=False)
|
|
61
|
+
is_l1d: bool = False
|
|
62
|
+
offsets: InitVar[np.ndarray] = None
|
|
63
|
+
timedelta: InitVar[np.ndarray] = None
|
|
64
|
+
|
|
65
|
+
def __post_init__(self, offsets: np.ndarray, timedelta: np.ndarray) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Calculate the magnitude of the vectors after initialization.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
offsets : np.ndarray
|
|
72
|
+
Offsets to apply to the vectors. Should be of shape (n, 3) where n is the
|
|
73
|
+
number of vectors.
|
|
74
|
+
timedelta : np.ndarray
|
|
75
|
+
Time deltas to shift the timestamps by. Should be of length n.
|
|
76
|
+
Given in seconds.
|
|
77
|
+
"""
|
|
78
|
+
if offsets is not None:
|
|
79
|
+
self.vectors = self.apply_offsets(self.vectors, offsets)
|
|
80
|
+
if timedelta is not None:
|
|
81
|
+
self.epoch = self.shift_timestamps(self.epoch, timedelta)
|
|
82
|
+
|
|
83
|
+
self.magnitude = self.calculate_magnitude(self.vectors)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def calculate_magnitude(
|
|
87
|
+
vectors: np.ndarray,
|
|
88
|
+
) -> np.ndarray:
|
|
89
|
+
"""
|
|
90
|
+
Given a list of vectors (x, y, z), calculate the magnitude of each vector.
|
|
91
|
+
|
|
92
|
+
For an input list of vectors of size (n, 3) returns a list of magnitudes of
|
|
93
|
+
size (n,).
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
vectors : np.ndarray
|
|
98
|
+
Array of vectors to calculate the magnitude of.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
np.ndarray
|
|
103
|
+
Array of magnitudes of the input vectors.
|
|
104
|
+
"""
|
|
105
|
+
return np.zeros(vectors.shape[0]) # type: ignore
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def apply_offsets(vectors: np.ndarray, offsets: np.ndarray) -> np.ndarray:
|
|
109
|
+
"""
|
|
110
|
+
Apply the offsets to the vectors by adding them together.
|
|
111
|
+
|
|
112
|
+
These offsets are used to shift the vectors in the x, y, and z directions.
|
|
113
|
+
They can either be provided through a custom offsets datafile, or calculated
|
|
114
|
+
using a gradiometry algorithm.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
vectors : np.ndarray
|
|
119
|
+
Array of vectors to apply the offsets to. Should be of shape (n, 3) where n
|
|
120
|
+
is the number of vectors.
|
|
121
|
+
offsets : np.ndarray
|
|
122
|
+
Array of offsets to apply to the vectors. Should be of shape (n, 3) where n
|
|
123
|
+
is the number of vectors.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
np.ndarray
|
|
128
|
+
Array of vectors with offsets applied. Should be of shape (n, 3).
|
|
129
|
+
"""
|
|
130
|
+
if vectors.shape[0] != offsets.shape[0]:
|
|
131
|
+
raise ValueError("Vectors and offsets must have the same length.")
|
|
132
|
+
|
|
133
|
+
offset_vectors: np.ndarray = vectors[:, :3] + offsets
|
|
134
|
+
|
|
135
|
+
# TODO: CDF files don't have NaNs. Emailed MAG to ask what this will look like.
|
|
136
|
+
# Any values where offsets is nan must also be nan
|
|
137
|
+
offset_vectors[np.isnan(offsets).any(axis=1)] = np.nan
|
|
138
|
+
|
|
139
|
+
return offset_vectors
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def shift_timestamps(epoch: np.ndarray, timedelta: np.ndarray) -> np.ndarray:
|
|
143
|
+
"""
|
|
144
|
+
Shift the timestamps by the given timedelta.
|
|
145
|
+
|
|
146
|
+
If timedelta is positive, the epochs are shifted forward in time.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
epoch : np.ndarray
|
|
151
|
+
Array of timestamps to shift. Should be of length n.
|
|
152
|
+
timedelta : np.ndarray
|
|
153
|
+
Array of time deltas to shift the timestamps by. Should be the same length
|
|
154
|
+
as epoch. Given in seconds.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
np.ndarray
|
|
159
|
+
Shifted timestamps.
|
|
160
|
+
"""
|
|
161
|
+
if epoch.shape[0] != timedelta.shape[0]:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
"Input Epoch and offsets timedeltas must be the same length."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
timedelta_ns = timedelta * 1e9
|
|
167
|
+
shifted_timestamps = epoch + timedelta_ns
|
|
168
|
+
return shifted_timestamps
|
|
169
|
+
|
|
170
|
+
def truncate_to_24h(self, timestamp: str) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Truncate all data to a 24 hour period.
|
|
173
|
+
|
|
174
|
+
24 hours is given by timestamp in the format YYYYmmdd.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
timestamp : str
|
|
179
|
+
Timestamp in the format YYYYMMDD.
|
|
180
|
+
"""
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
def generate_dataset(
|
|
184
|
+
self,
|
|
185
|
+
attribute_manager: ImapCdfAttributes,
|
|
186
|
+
frame: ValidFrames = ValidFrames.dsrf,
|
|
187
|
+
) -> xr.Dataset:
|
|
188
|
+
"""
|
|
189
|
+
Generate an xarray dataset from the dataclass.
|
|
190
|
+
|
|
191
|
+
This method can be used for L2 and L1D, since they have extremely similar
|
|
192
|
+
output.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
attribute_manager : ImapCdfAttributes
|
|
197
|
+
CDF attributes object for the correct level.
|
|
198
|
+
frame : ValidFrames
|
|
199
|
+
SPICE reference frame to rotate the data into.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
xr.Dataset
|
|
204
|
+
Complete dataset ready to write to CDF file.
|
|
205
|
+
"""
|
|
206
|
+
logical_source_id = f"imap_mag_l2_{self.data_mode.value.lower()}-{frame.name}"
|
|
207
|
+
direction = xr.DataArray(
|
|
208
|
+
np.arange(3),
|
|
209
|
+
name="direction",
|
|
210
|
+
dims=["direction"],
|
|
211
|
+
attrs=attribute_manager.get_variable_attributes(
|
|
212
|
+
"direction_attrs", check_schema=False
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
direction_label = xr.DataArray(
|
|
217
|
+
direction.values.astype(str),
|
|
218
|
+
name="direction_label",
|
|
219
|
+
dims=["direction_label"],
|
|
220
|
+
attrs=attribute_manager.get_variable_attributes(
|
|
221
|
+
"direction_label", check_schema=False
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
epoch_time = xr.DataArray(
|
|
226
|
+
self.epoch,
|
|
227
|
+
name="epoch",
|
|
228
|
+
dims=["epoch"],
|
|
229
|
+
attrs=attribute_manager.get_variable_attributes("epoch"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
vectors = xr.DataArray(
|
|
233
|
+
self.vectors,
|
|
234
|
+
name="vectors",
|
|
235
|
+
dims=["epoch", "direction"],
|
|
236
|
+
attrs=attribute_manager.get_variable_attributes("vector_attrs"),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
quality_flags = xr.DataArray(
|
|
240
|
+
self.quality_flags,
|
|
241
|
+
name="quality_flags",
|
|
242
|
+
dims=["epoch"],
|
|
243
|
+
attrs=attribute_manager.get_variable_attributes("compression"),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
quality_bitmask = xr.DataArray(
|
|
247
|
+
self.quality_flags,
|
|
248
|
+
name="quality_flags",
|
|
249
|
+
dims=["epoch"],
|
|
250
|
+
attrs=attribute_manager.get_variable_attributes("compression"),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
rng = xr.DataArray(
|
|
254
|
+
self.range,
|
|
255
|
+
name="range",
|
|
256
|
+
dims=["epoch"],
|
|
257
|
+
# TODO temp attrs
|
|
258
|
+
attrs=attribute_manager.get_variable_attributes("compression_width"),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
magnitude = xr.DataArray(
|
|
262
|
+
self.magnitude,
|
|
263
|
+
name="magnitude",
|
|
264
|
+
dims=["epoch"],
|
|
265
|
+
attrs=attribute_manager.get_variable_attributes("compression_width"),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
global_attributes = (
|
|
269
|
+
attribute_manager.get_global_attributes(logical_source_id)
|
|
270
|
+
| self.global_attributes
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
output = xr.Dataset(
|
|
274
|
+
coords={
|
|
275
|
+
"epoch": epoch_time,
|
|
276
|
+
"direction": direction,
|
|
277
|
+
"direction_label": direction_label,
|
|
278
|
+
},
|
|
279
|
+
attrs=global_attributes,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
output["vectors"] = vectors
|
|
283
|
+
output["quality_flags"] = quality_flags
|
|
284
|
+
output["quality_bitmask"] = quality_bitmask
|
|
285
|
+
output["range"] = rng
|
|
286
|
+
output["magnitude"] = magnitude
|
|
287
|
+
|
|
288
|
+
return output
|
|
@@ -66,7 +66,7 @@ def assemble_quaternions(ds: xr.Dataset) -> xr.Dataset:
|
|
|
66
66
|
base_name = "FSW_ACS_QUAT_10_HZ_BUFFERED".lower()
|
|
67
67
|
for quat_i, label in enumerate(["x", "y", "z", "s"]):
|
|
68
68
|
# 0, 1, 2, .. 9 // 10, 11, 12, .. 19 // 20, 21, 22, .. 29 // 30, 31, 32, .. 39
|
|
69
|
-
names = [f"{base_name}_{i + quat_i*10}" for i in range(10)]
|
|
69
|
+
names = [f"{base_name}_{i + quat_i * 10}" for i in range(10)]
|
|
70
70
|
quat = np.stack([ds[name] for name in names], axis=1).ravel()
|
|
71
71
|
output_ds[f"quat_{label}"] = ("epoch", quat)
|
|
72
72
|
return output_ds
|
|
@@ -100,8 +100,6 @@ def process_quaternions(packet_file: Path | str) -> tuple[xr.Dataset, xr.Dataset
|
|
|
100
100
|
# Update dataset global attributes
|
|
101
101
|
attr_mgr = ImapCdfAttributes()
|
|
102
102
|
attr_mgr.add_instrument_global_attrs("spacecraft")
|
|
103
|
-
# TODO: Allow version to be passed in
|
|
104
|
-
attr_mgr.add_global_attribute("Data_version", 1)
|
|
105
103
|
attr_mgr.add_instrument_variable_attrs(instrument="spacecraft", level=None)
|
|
106
104
|
|
|
107
105
|
l1a_ds.attrs.update(
|
|
@@ -63,7 +63,7 @@ class SpiceFrame(IntEnum):
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
BORESIGHT_LOOKUP = {
|
|
66
|
-
SpiceFrame.
|
|
66
|
+
SpiceFrame.IMAP_LO_BASE: np.array([0, -1, 0]),
|
|
67
67
|
SpiceFrame.IMAP_HI_45: np.array([0, 1, 0]),
|
|
68
68
|
SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
|
|
69
69
|
SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
|
|
@@ -136,7 +136,7 @@ def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> fl
|
|
|
136
136
|
"""
|
|
137
137
|
# TODO: Implement retrieval from SPICE?
|
|
138
138
|
offset_lookup = {
|
|
139
|
-
SpiceFrame.
|
|
139
|
+
SpiceFrame.IMAP_LO_BASE: 330 / 360,
|
|
140
140
|
SpiceFrame.IMAP_HI_45: 255 / 360,
|
|
141
141
|
SpiceFrame.IMAP_HI_90: 285 / 360,
|
|
142
142
|
SpiceFrame.IMAP_ULTRA_45: 33 / 360,
|
|
@@ -325,7 +325,7 @@ def instrument_pointing(
|
|
|
325
325
|
"""
|
|
326
326
|
Compute the instrument pointing at the specified times.
|
|
327
327
|
|
|
328
|
-
By default, the coordinates returned are
|
|
328
|
+
By default, the coordinates returned are (Longitude, Latitude) coordinates in
|
|
329
329
|
the reference frame `to_frame`. Cartesian coordinates can be returned if
|
|
330
330
|
desired by setting `cartesian=True`.
|
|
331
331
|
|
imap_processing/spice/kernels.py
CHANGED
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
import functools
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
-
from collections.abc import Generator
|
|
7
|
-
from contextlib import contextmanager
|
|
8
|
-
from pathlib import Path
|
|
9
6
|
from typing import Any, Callable, Optional, Union, overload
|
|
10
7
|
|
|
11
|
-
import numpy as np
|
|
12
8
|
import spiceypy
|
|
13
|
-
from numpy.typing import NDArray
|
|
14
9
|
from spiceypy.utils.exceptions import SpiceyError
|
|
15
10
|
|
|
16
11
|
from imap_processing import imap_module_directory
|
|
@@ -181,277 +176,6 @@ def ensure_spice(
|
|
|
181
176
|
return _decorator
|
|
182
177
|
|
|
183
178
|
|
|
184
|
-
@contextmanager
|
|
185
|
-
def open_spice_ck_file(pointing_frame_path: Path) -> Generator[int, None, None]:
|
|
186
|
-
"""
|
|
187
|
-
Context manager for handling SPICE CK files.
|
|
188
|
-
|
|
189
|
-
Parameters
|
|
190
|
-
----------
|
|
191
|
-
pointing_frame_path : str
|
|
192
|
-
Path to the CK file.
|
|
193
|
-
|
|
194
|
-
Yields
|
|
195
|
-
------
|
|
196
|
-
handle : int
|
|
197
|
-
Handle to the opened CK file.
|
|
198
|
-
"""
|
|
199
|
-
# TODO: We will need to figure out if ck kernel changes
|
|
200
|
-
# and how that will affect appending to the pointing
|
|
201
|
-
# frame kernel.
|
|
202
|
-
if pointing_frame_path.exists():
|
|
203
|
-
handle = spiceypy.dafopw(str(pointing_frame_path))
|
|
204
|
-
else:
|
|
205
|
-
handle = spiceypy.ckopn(str(pointing_frame_path), "CK", 0)
|
|
206
|
-
try:
|
|
207
|
-
yield handle
|
|
208
|
-
finally:
|
|
209
|
-
spiceypy.ckcls(handle)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
@ensure_spice
|
|
213
|
-
def create_pointing_frame(pointing_frame_path: Path, ck_path: Path) -> None:
|
|
214
|
-
"""
|
|
215
|
-
Create the pointing frame.
|
|
216
|
-
|
|
217
|
-
Parameters
|
|
218
|
-
----------
|
|
219
|
-
pointing_frame_path : pathlib.Path
|
|
220
|
-
Location of pointing frame kernel.
|
|
221
|
-
ck_path : pathlib.Path
|
|
222
|
-
Location of the CK kernel.
|
|
223
|
-
|
|
224
|
-
Notes
|
|
225
|
-
-----
|
|
226
|
-
Kernels required to be furnished:
|
|
227
|
-
"imap_science_0001.tf",
|
|
228
|
-
"imap_sclk_0000.tsc",
|
|
229
|
-
"imap_sim_ck_2hr_2secsampling_with_nutation.bc" or
|
|
230
|
-
"sim_1yr_imap_attitude.bc",
|
|
231
|
-
"imap_wkcp.tf",
|
|
232
|
-
"naif0012.tls"
|
|
233
|
-
|
|
234
|
-
Assumptions:
|
|
235
|
-
- The MOC has removed timeframe in which nutation/procession are present.
|
|
236
|
-
TODO: We may come back and have a check for this.
|
|
237
|
-
- We will continue to append to the pointing frame kernel.
|
|
238
|
-
TODO: Figure out how we want to handle the file size becoming too large.
|
|
239
|
-
- For now we can only furnish a single ck kernel.
|
|
240
|
-
TODO: This will not be the case once we add the ability to query the .csv.
|
|
241
|
-
|
|
242
|
-
References
|
|
243
|
-
----------
|
|
244
|
-
https://numpydoc.readthedocs.io/en/latest/format.html#references
|
|
245
|
-
"""
|
|
246
|
-
# Get IDs.
|
|
247
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.gipool
|
|
248
|
-
id_imap_dps = spiceypy.gipool("FRAME_IMAP_DPS", 0, 1)
|
|
249
|
-
id_imap_sclk = spiceypy.gipool("CK_-43000_SCLK", 0, 1)
|
|
250
|
-
|
|
251
|
-
# Verify that only ck_path kernel is loaded.
|
|
252
|
-
count = spiceypy.ktotal("ck")
|
|
253
|
-
loaded_ck_kernel, _, _, _ = spiceypy.kdata(count - 1, "ck")
|
|
254
|
-
|
|
255
|
-
if count != 1 or str(ck_path) != loaded_ck_kernel:
|
|
256
|
-
raise ValueError(f"Error: Expected CK kernel {ck_path}")
|
|
257
|
-
|
|
258
|
-
# If the pointing frame kernel already exists, find the last time.
|
|
259
|
-
if pointing_frame_path.exists():
|
|
260
|
-
# Get the last time in the pointing frame kernel.
|
|
261
|
-
pointing_cover = spiceypy.ckcov(
|
|
262
|
-
str(pointing_frame_path), int(id_imap_dps), True, "SEGMENT", 0, "TDB"
|
|
263
|
-
)
|
|
264
|
-
num_segments = spiceypy.wncard(pointing_cover)
|
|
265
|
-
_, et_end_pointing_frame = spiceypy.wnfetd(pointing_cover, num_segments - 1)
|
|
266
|
-
else:
|
|
267
|
-
et_end_pointing_frame = None
|
|
268
|
-
|
|
269
|
-
# TODO: Query for .csv file to get the pointing start and end times.
|
|
270
|
-
# TODO: Remove next four lines once query is added.
|
|
271
|
-
id_imap_spacecraft = spiceypy.gipool("FRAME_IMAP_SPACECRAFT", 0, 1)
|
|
272
|
-
ck_cover = spiceypy.ckcov(
|
|
273
|
-
str(ck_path), int(id_imap_spacecraft), True, "INTERVAL", 0, "TDB"
|
|
274
|
-
)
|
|
275
|
-
num_intervals = spiceypy.wncard(ck_cover)
|
|
276
|
-
|
|
277
|
-
with open_spice_ck_file(pointing_frame_path) as handle:
|
|
278
|
-
# TODO: this will change to the number of pointings.
|
|
279
|
-
for i in range(num_intervals):
|
|
280
|
-
# Get the coverage window
|
|
281
|
-
# TODO: this will change to pointing start and end time.
|
|
282
|
-
et_start, et_end = spiceypy.wnfetd(ck_cover, i)
|
|
283
|
-
et_times = _get_et_times(et_start, et_end)
|
|
284
|
-
|
|
285
|
-
# TODO: remove after query is added.
|
|
286
|
-
if (
|
|
287
|
-
et_end_pointing_frame is not None
|
|
288
|
-
and et_times[0] < et_end_pointing_frame
|
|
289
|
-
):
|
|
290
|
-
break
|
|
291
|
-
|
|
292
|
-
# Create a rotation matrix
|
|
293
|
-
rotation_matrix = _create_rotation_matrix(et_times)
|
|
294
|
-
|
|
295
|
-
# Convert the rotation matrix to a quaternion.
|
|
296
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
|
|
297
|
-
q_avg = spiceypy.m2q(rotation_matrix)
|
|
298
|
-
|
|
299
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.sce2c
|
|
300
|
-
# Convert start and end times to SCLK.
|
|
301
|
-
sclk_begtim = spiceypy.sce2c(int(id_imap_sclk), et_times[0])
|
|
302
|
-
sclk_endtim = spiceypy.sce2c(int(id_imap_sclk), et_times[-1])
|
|
303
|
-
|
|
304
|
-
# Create the pointing frame kernel.
|
|
305
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.ckw02
|
|
306
|
-
spiceypy.ckw02(
|
|
307
|
-
# Handle of an open CK file.
|
|
308
|
-
handle,
|
|
309
|
-
# Start time of the segment.
|
|
310
|
-
sclk_begtim,
|
|
311
|
-
# End time of the segment.
|
|
312
|
-
sclk_endtim,
|
|
313
|
-
# Pointing frame ID.
|
|
314
|
-
int(id_imap_dps),
|
|
315
|
-
# Reference frame.
|
|
316
|
-
"ECLIPJ2000", # Reference frame
|
|
317
|
-
# Identifier.
|
|
318
|
-
"IMAP_DPS",
|
|
319
|
-
# Number of pointing intervals.
|
|
320
|
-
1,
|
|
321
|
-
# Start times of individual pointing records within segment.
|
|
322
|
-
# Since there is only a single record this is equal to sclk_begtim.
|
|
323
|
-
np.array([sclk_begtim]),
|
|
324
|
-
# End times of individual pointing records within segment.
|
|
325
|
-
# Since there is only a single record this is equal to sclk_endtim.
|
|
326
|
-
np.array([sclk_endtim]), # Single stop time
|
|
327
|
-
# Average quaternion.
|
|
328
|
-
q_avg,
|
|
329
|
-
# 0.0 Angular rotation terms.
|
|
330
|
-
np.array([0.0, 0.0, 0.0]),
|
|
331
|
-
# Rates (seconds per tick) at which the quaternion and
|
|
332
|
-
# angular velocity change.
|
|
333
|
-
np.array([1.0]),
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def _get_et_times(et_start: float, et_end: float) -> NDArray[np.float64]:
|
|
338
|
-
"""
|
|
339
|
-
Get times for pointing start and stop.
|
|
340
|
-
|
|
341
|
-
Parameters
|
|
342
|
-
----------
|
|
343
|
-
et_start : float
|
|
344
|
-
Pointing start time.
|
|
345
|
-
et_end : float
|
|
346
|
-
Pointing end time.
|
|
347
|
-
|
|
348
|
-
Returns
|
|
349
|
-
-------
|
|
350
|
-
et_times : numpy.ndarray
|
|
351
|
-
Array of times between et_start and et_end.
|
|
352
|
-
"""
|
|
353
|
-
# TODO: Queried pointing start and stop times here.
|
|
354
|
-
# TODO removing the @ensure_spice decorator when using the repointing table.
|
|
355
|
-
|
|
356
|
-
# 1 spin/15 seconds; 10 quaternions / spin.
|
|
357
|
-
num_samples = (et_end - et_start) / 15 * 10
|
|
358
|
-
# There were rounding errors when using spiceypy.pxform so np.ceil and np.floor
|
|
359
|
-
# were used to ensure the start and end times were included in the array.
|
|
360
|
-
et_times = np.linspace(
|
|
361
|
-
np.ceil(et_start * 1e6) / 1e6, np.floor(et_end * 1e6) / 1e6, int(num_samples)
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
return et_times
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
@ensure_spice
|
|
368
|
-
def _average_quaternions(et_times: np.ndarray) -> NDArray:
|
|
369
|
-
"""
|
|
370
|
-
Average the quaternions.
|
|
371
|
-
|
|
372
|
-
Parameters
|
|
373
|
-
----------
|
|
374
|
-
et_times : numpy.ndarray
|
|
375
|
-
Array of times between et_start and et_end.
|
|
376
|
-
|
|
377
|
-
Returns
|
|
378
|
-
-------
|
|
379
|
-
q_avg : np.ndarray
|
|
380
|
-
Average quaternion.
|
|
381
|
-
"""
|
|
382
|
-
aggregate = np.zeros((4, 4))
|
|
383
|
-
for tdb in et_times:
|
|
384
|
-
# we use a quick and dirty method here for grabbing the quaternions
|
|
385
|
-
# from the attitude kernel. Depending on how well the kernel input
|
|
386
|
-
# data is built and sampled, there may or may not be aliasing with this
|
|
387
|
-
# approach. If it turns out that we need to pull the quaternions
|
|
388
|
-
# directly from the CK there are several routines that exist to do this
|
|
389
|
-
# but it's not straight forward. We'll revisit this if needed.
|
|
390
|
-
|
|
391
|
-
# Rotation matrix from IMAP spacecraft frame to ECLIPJ2000.
|
|
392
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.pxform
|
|
393
|
-
body_rots = spiceypy.pxform("IMAP_SPACECRAFT", "ECLIPJ2000", tdb)
|
|
394
|
-
# Convert rotation matrix to quaternion.
|
|
395
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
|
|
396
|
-
body_quat = spiceypy.m2q(body_rots)
|
|
397
|
-
|
|
398
|
-
# Standardize the quaternion so that they may be compared.
|
|
399
|
-
body_quat = body_quat * np.sign(body_quat[0])
|
|
400
|
-
# Aggregate quaternions into a single matrix.
|
|
401
|
-
aggregate += np.outer(body_quat, body_quat)
|
|
402
|
-
|
|
403
|
-
# Reference: "On Averaging Rotations".
|
|
404
|
-
# Link: https://link.springer.com/content/pdf/10.1023/A:1011129215388.pdf
|
|
405
|
-
aggregate /= len(et_times)
|
|
406
|
-
|
|
407
|
-
# Compute eigen values and vectors of the matrix A
|
|
408
|
-
# Eigenvalues tell you how much "influence" each
|
|
409
|
-
# direction (eigenvector) has.
|
|
410
|
-
# The largest eigenvalue corresponds to the direction
|
|
411
|
-
# that has the most influence.
|
|
412
|
-
# The eigenvector corresponding to the largest
|
|
413
|
-
# eigenvalue points in the direction that has the most
|
|
414
|
-
# combined rotation influence.
|
|
415
|
-
eigvals, eigvecs = np.linalg.eig(aggregate)
|
|
416
|
-
# q0: The scalar part of the quaternion.
|
|
417
|
-
# q1, q2, q3: The vector part of the quaternion.
|
|
418
|
-
q_avg = eigvecs[:, np.argmax(eigvals)]
|
|
419
|
-
|
|
420
|
-
return q_avg
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
def _create_rotation_matrix(et_times: np.ndarray) -> NDArray:
|
|
424
|
-
"""
|
|
425
|
-
Create a rotation matrix.
|
|
426
|
-
|
|
427
|
-
Parameters
|
|
428
|
-
----------
|
|
429
|
-
et_times : numpy.ndarray
|
|
430
|
-
Array of times between et_start and et_end.
|
|
431
|
-
|
|
432
|
-
Returns
|
|
433
|
-
-------
|
|
434
|
-
rotation_matrix : np.ndarray
|
|
435
|
-
Rotation matrix.
|
|
436
|
-
"""
|
|
437
|
-
# Averaged quaternions.
|
|
438
|
-
q_avg = _average_quaternions(et_times)
|
|
439
|
-
|
|
440
|
-
# Converts the averaged quaternion (q_avg) into a rotation matrix
|
|
441
|
-
# and get inertial z axis.
|
|
442
|
-
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.q2m
|
|
443
|
-
z_avg = spiceypy.q2m(list(q_avg))[:, 2]
|
|
444
|
-
# y_avg is perpendicular to both z_avg and the standard Z-axis.
|
|
445
|
-
y_avg = np.cross(z_avg, [0, 0, 1])
|
|
446
|
-
# x_avg is perpendicular to y_avg and z_avg.
|
|
447
|
-
x_avg = np.cross(y_avg, z_avg)
|
|
448
|
-
|
|
449
|
-
# Construct the rotation matrix from x_avg, y_avg, z_avg
|
|
450
|
-
rotation_matrix = np.asarray([x_avg, y_avg, z_avg])
|
|
451
|
-
|
|
452
|
-
return rotation_matrix
|
|
453
|
-
|
|
454
|
-
|
|
455
179
|
def furnish_time_kernel() -> None:
|
|
456
180
|
"""Furnish the time kernels."""
|
|
457
181
|
spice_test_data_path = imap_module_directory / "tests/spice/test_data"
|