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,257 @@
|
|
|
1
|
+
"""Functions for retrieving repointing table data."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import spiceypy
|
|
11
|
+
from numpy.typing import NDArray
|
|
12
|
+
|
|
13
|
+
from imap_processing.spice.kernels import ensure_spice
|
|
14
|
+
from imap_processing.spice.time import met_to_sclkticks, sct_to_et
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@contextmanager
|
|
20
|
+
def open_spice_ck_file(pointing_frame_path: Path) -> Generator[int, None, None]:
|
|
21
|
+
"""
|
|
22
|
+
Context manager for handling SPICE CK files.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
pointing_frame_path : str
|
|
27
|
+
Path to the CK file.
|
|
28
|
+
|
|
29
|
+
Yields
|
|
30
|
+
------
|
|
31
|
+
handle : int
|
|
32
|
+
Handle to the opened CK file.
|
|
33
|
+
"""
|
|
34
|
+
if pointing_frame_path.exists():
|
|
35
|
+
handle = spiceypy.dafopw(str(pointing_frame_path))
|
|
36
|
+
else:
|
|
37
|
+
handle = spiceypy.ckopn(str(pointing_frame_path), "CK", 0)
|
|
38
|
+
try:
|
|
39
|
+
yield handle
|
|
40
|
+
finally:
|
|
41
|
+
spiceypy.ckcls(handle)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@typing.no_type_check
|
|
45
|
+
@ensure_spice
|
|
46
|
+
def create_pointing_frame(
|
|
47
|
+
pointing_frame_path: Path,
|
|
48
|
+
ck_path: Path,
|
|
49
|
+
repoint_start_met: NDArray,
|
|
50
|
+
repoint_end_met: NDArray,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Create the pointing frame.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
pointing_frame_path : pathlib.Path
|
|
58
|
+
Location of pointing frame kernel.
|
|
59
|
+
ck_path : pathlib.Path
|
|
60
|
+
Location of the CK kernel.
|
|
61
|
+
repoint_start_met : numpy.ndarray
|
|
62
|
+
Start time of the repointing in MET.
|
|
63
|
+
repoint_end_met : numpy.ndarray
|
|
64
|
+
End time of the repointing in MET.
|
|
65
|
+
|
|
66
|
+
Notes
|
|
67
|
+
-----
|
|
68
|
+
Kernels required to be furnished:
|
|
69
|
+
"imap_science_0001.tf",
|
|
70
|
+
"imap_sclk_0000.tsc",
|
|
71
|
+
"imap_sim_ck_2hr_2secsampling_with_nutation.bc" or
|
|
72
|
+
"sim_1yr_imap_attitude.bc",
|
|
73
|
+
"imap_wkcp.tf",
|
|
74
|
+
"naif0012.tls"
|
|
75
|
+
|
|
76
|
+
Assumptions:
|
|
77
|
+
- The MOC has removed timeframe in which nutation/procession are present.
|
|
78
|
+
TODO: We may come back and have a check for this.
|
|
79
|
+
- The pointing frame kernel is made based on the most recent ck kernel.
|
|
80
|
+
In other words 1:1 ratio.
|
|
81
|
+
"""
|
|
82
|
+
# Get IDs.
|
|
83
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.gipool
|
|
84
|
+
id_imap_dps = spiceypy.gipool("FRAME_IMAP_DPS", 0, 1)
|
|
85
|
+
id_imap_sclk = spiceypy.gipool("CK_-43000_SCLK", 0, 1)
|
|
86
|
+
|
|
87
|
+
# Verify that only ck_path kernel is loaded.
|
|
88
|
+
count = spiceypy.ktotal("ck")
|
|
89
|
+
loaded_ck_kernel, _, _, _ = spiceypy.kdata(count - 1, "ck")
|
|
90
|
+
|
|
91
|
+
if count != 1 or str(ck_path) != loaded_ck_kernel:
|
|
92
|
+
raise ValueError(f"Error: Expected CK kernel {ck_path}")
|
|
93
|
+
|
|
94
|
+
id_imap_spacecraft = spiceypy.gipool("FRAME_IMAP_SPACECRAFT", 0, 1)
|
|
95
|
+
|
|
96
|
+
# Select only the pointings within the attitude coverage.
|
|
97
|
+
ck_cover = spiceypy.ckcov(
|
|
98
|
+
str(ck_path), int(id_imap_spacecraft), True, "INTERVAL", 0, "TDB"
|
|
99
|
+
)
|
|
100
|
+
num_intervals = spiceypy.wncard(ck_cover)
|
|
101
|
+
et_start, _ = spiceypy.wnfetd(ck_cover, 0)
|
|
102
|
+
_, et_end = spiceypy.wnfetd(ck_cover, num_intervals - 1)
|
|
103
|
+
|
|
104
|
+
sclk_ticks_start = met_to_sclkticks(repoint_start_met)
|
|
105
|
+
et_start_repoint = sct_to_et(sclk_ticks_start)
|
|
106
|
+
sclk_ticks_end = met_to_sclkticks(repoint_end_met)
|
|
107
|
+
et_end_repoint = sct_to_et(sclk_ticks_end)
|
|
108
|
+
|
|
109
|
+
valid_mask = (et_start_repoint >= et_start) & (et_end_repoint <= et_end)
|
|
110
|
+
et_start_repoint = et_start_repoint[valid_mask]
|
|
111
|
+
et_end_repoint = et_end_repoint[valid_mask]
|
|
112
|
+
|
|
113
|
+
with open_spice_ck_file(pointing_frame_path) as handle:
|
|
114
|
+
for i in range(len(repoint_start_met)):
|
|
115
|
+
# 1 spin/15 seconds; 10 quaternions / spin.
|
|
116
|
+
num_samples = (et_end_repoint[i] - et_start_repoint[i]) / 15 * 10
|
|
117
|
+
# There were rounding errors when using spiceypy.pxform
|
|
118
|
+
# so np.ceil and np.floor were used to ensure the start
|
|
119
|
+
# and end times were within the ck range.
|
|
120
|
+
et_times = np.linspace(
|
|
121
|
+
np.ceil(et_start_repoint[i] * 1e6) / 1e6,
|
|
122
|
+
np.floor(et_end_repoint[i] * 1e6) / 1e6,
|
|
123
|
+
int(num_samples),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Create a rotation matrix
|
|
127
|
+
rotation_matrix = _create_rotation_matrix(et_times)
|
|
128
|
+
|
|
129
|
+
# Convert the rotation matrix to a quaternion.
|
|
130
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
|
|
131
|
+
q_avg = spiceypy.m2q(rotation_matrix)
|
|
132
|
+
|
|
133
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.sce2c
|
|
134
|
+
# Convert start and end times to SCLK.
|
|
135
|
+
sclk_begtim = spiceypy.sce2c(int(id_imap_sclk), et_times[0])
|
|
136
|
+
sclk_endtim = spiceypy.sce2c(int(id_imap_sclk), et_times[-1])
|
|
137
|
+
|
|
138
|
+
# Create the pointing frame kernel.
|
|
139
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.ckw02
|
|
140
|
+
spiceypy.ckw02(
|
|
141
|
+
# Handle of an open CK file.
|
|
142
|
+
handle,
|
|
143
|
+
# Start time of the segment.
|
|
144
|
+
sclk_begtim,
|
|
145
|
+
# End time of the segment.
|
|
146
|
+
sclk_endtim,
|
|
147
|
+
# Pointing frame ID.
|
|
148
|
+
int(id_imap_dps),
|
|
149
|
+
# Reference frame.
|
|
150
|
+
"ECLIPJ2000", # Reference frame
|
|
151
|
+
# Identifier.
|
|
152
|
+
"IMAP_DPS",
|
|
153
|
+
# Number of pointing intervals.
|
|
154
|
+
1,
|
|
155
|
+
# Start times of individual pointing records within segment.
|
|
156
|
+
# Since there is only a single record this is equal to sclk_begtim.
|
|
157
|
+
np.array([sclk_begtim]),
|
|
158
|
+
# End times of individual pointing records within segment.
|
|
159
|
+
# Since there is only a single record this is equal to sclk_endtim.
|
|
160
|
+
np.array([sclk_endtim]), # Single stop time
|
|
161
|
+
# Average quaternion.
|
|
162
|
+
q_avg,
|
|
163
|
+
# 0.0 Angular rotation terms.
|
|
164
|
+
np.array([0.0, 0.0, 0.0]),
|
|
165
|
+
# Rates (seconds per tick) at which the quaternion and
|
|
166
|
+
# angular velocity change.
|
|
167
|
+
np.array([1.0]),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@typing.no_type_check
|
|
172
|
+
@ensure_spice
|
|
173
|
+
def _average_quaternions(et_times: np.ndarray) -> NDArray:
|
|
174
|
+
"""
|
|
175
|
+
Average the quaternions.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
et_times : numpy.ndarray
|
|
180
|
+
Array of times between et_start and et_end.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
q_avg : np.ndarray
|
|
185
|
+
Average quaternion.
|
|
186
|
+
"""
|
|
187
|
+
aggregate = np.zeros((4, 4))
|
|
188
|
+
for tdb in et_times:
|
|
189
|
+
# we use a quick and dirty method here for grabbing the quaternions
|
|
190
|
+
# from the attitude kernel. Depending on how well the kernel input
|
|
191
|
+
# data is built and sampled, there may or may not be aliasing with this
|
|
192
|
+
# approach. If it turns out that we need to pull the quaternions
|
|
193
|
+
# directly from the CK there are several routines that exist to do this
|
|
194
|
+
# but it's not straight forward. We'll revisit this if needed.
|
|
195
|
+
|
|
196
|
+
# Rotation matrix from IMAP spacecraft frame to ECLIPJ2000.
|
|
197
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.pxform
|
|
198
|
+
body_rots = spiceypy.pxform("IMAP_SPACECRAFT", "ECLIPJ2000", tdb)
|
|
199
|
+
# Convert rotation matrix to quaternion.
|
|
200
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
|
|
201
|
+
body_quat = spiceypy.m2q(body_rots)
|
|
202
|
+
|
|
203
|
+
# Standardize the quaternion so that they may be compared.
|
|
204
|
+
body_quat = body_quat * np.sign(body_quat[0])
|
|
205
|
+
# Aggregate quaternions into a single matrix.
|
|
206
|
+
aggregate += np.outer(body_quat, body_quat)
|
|
207
|
+
|
|
208
|
+
# Reference: "On Averaging Rotations".
|
|
209
|
+
# Link: https://link.springer.com/content/pdf/10.1023/A:1011129215388.pdf
|
|
210
|
+
aggregate /= len(et_times)
|
|
211
|
+
|
|
212
|
+
# Compute eigen values and vectors of the matrix A
|
|
213
|
+
# Eigenvalues tell you how much "influence" each
|
|
214
|
+
# direction (eigenvector) has.
|
|
215
|
+
# The largest eigenvalue corresponds to the direction
|
|
216
|
+
# that has the most influence.
|
|
217
|
+
# The eigenvector corresponding to the largest
|
|
218
|
+
# eigenvalue points in the direction that has the most
|
|
219
|
+
# combined rotation influence.
|
|
220
|
+
eigvals, eigvecs = np.linalg.eig(aggregate)
|
|
221
|
+
# q0: The scalar part of the quaternion.
|
|
222
|
+
# q1, q2, q3: The vector part of the quaternion.
|
|
223
|
+
q_avg = eigvecs[:, np.argmax(eigvals)]
|
|
224
|
+
|
|
225
|
+
return q_avg
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _create_rotation_matrix(et_times: np.ndarray) -> NDArray:
|
|
229
|
+
"""
|
|
230
|
+
Create a rotation matrix.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
et_times : numpy.ndarray
|
|
235
|
+
Array of times between et_start and et_end.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
rotation_matrix : np.ndarray
|
|
240
|
+
Rotation matrix.
|
|
241
|
+
"""
|
|
242
|
+
# Averaged quaternions.
|
|
243
|
+
q_avg = _average_quaternions(et_times)
|
|
244
|
+
|
|
245
|
+
# Converts the averaged quaternion (q_avg) into a rotation matrix
|
|
246
|
+
# and get inertial z axis.
|
|
247
|
+
# https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.q2m
|
|
248
|
+
z_avg = spiceypy.q2m(list(q_avg))[:, 2]
|
|
249
|
+
# y_avg is perpendicular to both z_avg and the standard Z-axis.
|
|
250
|
+
y_avg = np.cross(z_avg, [0, 0, 1])
|
|
251
|
+
# x_avg is perpendicular to y_avg and z_avg.
|
|
252
|
+
x_avg = np.cross(y_avg, z_avg)
|
|
253
|
+
|
|
254
|
+
# Construct the rotation matrix from x_avg, y_avg, z_avg
|
|
255
|
+
rotation_matrix = np.asarray([x_avg, y_avg, z_avg])
|
|
256
|
+
|
|
257
|
+
return rotation_matrix
|
imap_processing/spice/repoint.py
CHANGED
|
@@ -16,17 +16,32 @@ def get_repoint_data() -> pd.DataFrame:
|
|
|
16
16
|
"""
|
|
17
17
|
Read repointing file using environment variable and return as dataframe.
|
|
18
18
|
|
|
19
|
+
Pointing and repointing nomenclature can be confusing. In this case,
|
|
20
|
+
repoint is taken to mean a repoint maneuver. Thus, repoint_start and repoint_end
|
|
21
|
+
are the times that bound when the spacecraft is performing a repointing maneuver.
|
|
22
|
+
This is different from a pointing which is the time between repointing maneuvers.
|
|
23
|
+
|
|
19
24
|
REPOINT_DATA_FILEPATH environment variable should point to a local
|
|
20
25
|
file where the repointing csv file is located.
|
|
21
26
|
|
|
22
27
|
Returns
|
|
23
28
|
-------
|
|
24
|
-
repoint_df :
|
|
29
|
+
repoint_df : pandas.DataFrame
|
|
25
30
|
The repointing csv loaded into a pandas dataframe. The dataframe will
|
|
26
31
|
contain the following columns:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
|
|
33
|
+
* `repoint_start_sec_sclk`: Starting MET seconds of repoint maneuver.
|
|
34
|
+
* `repoint_start_subsec_sclk`: Starting MET microseconds of repoint
|
|
35
|
+
maneuver.
|
|
36
|
+
* `repoint_start_met`: Floating point MET of repoint maneuver start time.
|
|
37
|
+
Derived from `repoint_start_sec_sclk` and `repoint_start_subsec_sclk`.
|
|
38
|
+
* `repoint_start_utc`: UTC time of repoint maneuver start time.
|
|
39
|
+
* `repoint_end_sec_sclk`: Ending MET seconds of repoint maneuver.
|
|
40
|
+
* `repoint_end_subsec_sclk`: Ending MET microseconds of repoint maneuver.
|
|
41
|
+
* `repoint_end_met`: Floating point MET of repoint maneuver end time.
|
|
42
|
+
Derived from `repoint_end_sec_sclk` and `repoint_end_subsec_sclk`.
|
|
43
|
+
* `repoint_end_utc`: UTC time of repoint maneuver end time.
|
|
44
|
+
* `repoint_id`: Unique ID number of each repoint maneuver.
|
|
30
45
|
"""
|
|
31
46
|
repoint_data_filepath = os.getenv("REPOINT_DATA_FILEPATH")
|
|
32
47
|
if repoint_data_filepath is not None:
|
|
@@ -38,6 +53,15 @@ def get_repoint_data() -> pd.DataFrame:
|
|
|
38
53
|
logger.info(f"Reading repointing data from {path_to_spin_file}")
|
|
39
54
|
repoint_df = pd.read_csv(path_to_spin_file, comment="#")
|
|
40
55
|
|
|
56
|
+
# Compute times by combining seconds and subseconds fields
|
|
57
|
+
repoint_df["repoint_start_met"] = (
|
|
58
|
+
repoint_df["repoint_start_sec_sclk"]
|
|
59
|
+
+ repoint_df["repoint_start_subsec_sclk"] / 1e6
|
|
60
|
+
)
|
|
61
|
+
repoint_df["repoint_end_met"] = (
|
|
62
|
+
repoint_df["repoint_end_sec_sclk"] + repoint_df["repoint_end_subsec_sclk"] / 1e6
|
|
63
|
+
)
|
|
64
|
+
|
|
41
65
|
return repoint_df
|
|
42
66
|
|
|
43
67
|
|
|
@@ -68,15 +92,20 @@ def interpolate_repoint_data(
|
|
|
68
92
|
repoint_df : pandas.DataFrame
|
|
69
93
|
Repoint table data interpolated such that there is one row
|
|
70
94
|
for each of the queried MET times. Output columns are:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
|
|
96
|
+
* `repoint_start_sec_sclk`
|
|
97
|
+
* `repoint_start_subsec_sclk`
|
|
98
|
+
* `repoint_start_met`
|
|
99
|
+
* `repoint_end_sec_sclk`
|
|
100
|
+
* `repoint_end_subsec_sclk`
|
|
101
|
+
* `repoint_end_met`
|
|
102
|
+
* `repoint_id`
|
|
103
|
+
* `repoint_in_progress`
|
|
75
104
|
|
|
76
105
|
Raises
|
|
77
106
|
------
|
|
78
107
|
ValueError : If any of the query_met_times are before the first repoint
|
|
79
|
-
|
|
108
|
+
start time or after the last repoint start time plus 24-hours.
|
|
80
109
|
"""
|
|
81
110
|
repoint_df = get_repoint_data()
|
|
82
111
|
|
|
@@ -84,29 +113,29 @@ def interpolate_repoint_data(
|
|
|
84
113
|
query_met_times = np.atleast_1d(query_met_times)
|
|
85
114
|
|
|
86
115
|
# Make sure no query times are before the first repoint in the dataframe.
|
|
87
|
-
|
|
88
|
-
if np.any(query_met_times <
|
|
89
|
-
bad_times = query_met_times[query_met_times <
|
|
116
|
+
repoint_df_start_met = repoint_df["repoint_start_met"].values[0]
|
|
117
|
+
if np.any(query_met_times < repoint_df_start_met):
|
|
118
|
+
bad_times = query_met_times[query_met_times < repoint_df_start_met]
|
|
90
119
|
raise ValueError(
|
|
91
120
|
f"{bad_times.size} query times are before the first repoint start "
|
|
92
|
-
f" time in the repoint table. {bad_times=}, {
|
|
121
|
+
f" time in the repoint table. {bad_times=}, {repoint_df_start_met=}"
|
|
93
122
|
)
|
|
94
123
|
# Make sure that no query times are after the valid range of the dataframe.
|
|
95
124
|
# We approximate the end time of the table by adding 24 hours to the last
|
|
96
125
|
# known repoint start time.
|
|
97
|
-
|
|
98
|
-
if np.any(query_met_times >=
|
|
99
|
-
bad_times = query_met_times[query_met_times >=
|
|
126
|
+
repoint_df_end_met = repoint_df["repoint_start_met"].values[-1] + 24 * 60 * 60
|
|
127
|
+
if np.any(query_met_times >= repoint_df_end_met):
|
|
128
|
+
bad_times = query_met_times[query_met_times >= repoint_df_end_met]
|
|
100
129
|
raise ValueError(
|
|
101
130
|
f"{bad_times.size} query times are after the valid time of the "
|
|
102
131
|
f"pointing table. The valid end time is 24-hours after the last "
|
|
103
|
-
f"repoint_start_time. {bad_times=}, {
|
|
132
|
+
f"repoint_start_time. {bad_times=}, {repoint_df_end_met=}"
|
|
104
133
|
)
|
|
105
134
|
|
|
106
135
|
# Find the row index for each queried MET time such that:
|
|
107
136
|
# repoint_start_time[i] <= MET < repoint_start_time[i+1]
|
|
108
137
|
row_indices = (
|
|
109
|
-
np.searchsorted(repoint_df["
|
|
138
|
+
np.searchsorted(repoint_df["repoint_start_met"], query_met_times, side="right")
|
|
110
139
|
- 1
|
|
111
140
|
)
|
|
112
141
|
out_df = repoint_df.iloc[row_indices]
|
|
@@ -115,6 +144,6 @@ def interpolate_repoint_data(
|
|
|
115
144
|
# The table already has the correct row for each query time, so we
|
|
116
145
|
# only need to check if the query time is less than the repoint end time to
|
|
117
146
|
# get the same result as `repoint_start_time <= query_met_times < repoint_end_time`.
|
|
118
|
-
out_df["repoint_in_progress"] = query_met_times < out_df["
|
|
147
|
+
out_df["repoint_in_progress"] = query_met_times < out_df["repoint_end_met"].values
|
|
119
148
|
|
|
120
149
|
return out_df
|
imap_processing/spice/spin.py
CHANGED
|
@@ -22,20 +22,21 @@ def get_spin_data() -> pd.DataFrame:
|
|
|
22
22
|
It could be s3 filepath that can be used to download the data
|
|
23
23
|
through API or it could be path EFS or Batch volume mount path.
|
|
24
24
|
|
|
25
|
-
Spin data should contain the following fields:
|
|
26
|
-
* spin_number
|
|
27
|
-
* spin_start_sec
|
|
28
|
-
* spin_start_subsec
|
|
29
|
-
* spin_period_sec
|
|
30
|
-
* spin_period_valid
|
|
31
|
-
* spin_phase_valid
|
|
32
|
-
* spin_period_source
|
|
33
|
-
* thruster_firing
|
|
34
|
-
|
|
35
25
|
Returns
|
|
36
26
|
-------
|
|
37
27
|
spin_data : pandas.DataFrame
|
|
38
|
-
Spin data.
|
|
28
|
+
Spin data. The DataFrame will have the following columns:
|
|
29
|
+
|
|
30
|
+
* `spin_number`: Unique integer spin number.
|
|
31
|
+
* `spin_start_sec_sclk`: MET seconds of spin start time.
|
|
32
|
+
* `spin_start_subsec_sclk`: MET microseconds of spin start time.
|
|
33
|
+
* `spin_start_met`: Floating point MET seconds of spin start.
|
|
34
|
+
* `spin_start_utc`: UTC string of spin start time.
|
|
35
|
+
* `spin_period_sec`: Floating point spin period in seconds.
|
|
36
|
+
* `spin_period_valid`: Boolean indicating whether spin period is valid.
|
|
37
|
+
* `spin_phase_valid`: Boolean indicating whether spin phase is valid.
|
|
38
|
+
* `spin_period_source`: Source used for determining spin period.
|
|
39
|
+
* `thruster_firing`: Boolean indicating whether thruster is firing.
|
|
39
40
|
"""
|
|
40
41
|
spin_data_filepath = os.getenv("SPIN_DATA_FILEPATH")
|
|
41
42
|
if spin_data_filepath is not None:
|
|
@@ -44,11 +45,24 @@ def get_spin_data() -> pd.DataFrame:
|
|
|
44
45
|
# Handle the case where the environment variable is not set
|
|
45
46
|
raise ValueError("SPIN_DATA_FILEPATH environment variable is not set.")
|
|
46
47
|
|
|
47
|
-
spin_df = pd.read_csv(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
spin_df = pd.read_csv(
|
|
49
|
+
path_to_spin_file,
|
|
50
|
+
comment="#",
|
|
51
|
+
dtype={
|
|
52
|
+
"spin_number": int,
|
|
53
|
+
"spin_start_sec_sclk": int,
|
|
54
|
+
"spin_start_subsec_sclk": int,
|
|
55
|
+
"spin_start_utc": str,
|
|
56
|
+
"spin_period_sec": float,
|
|
57
|
+
"spin_period_valid": bool,
|
|
58
|
+
"spin_period_source": int,
|
|
59
|
+
"thruster_firing": bool,
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
# Combine spin_start_sec_sclk and spin_start_subsec_sclk to get the spin start
|
|
63
|
+
# time in seconds. The spin start subseconds are in microseconds.
|
|
64
|
+
spin_df["spin_start_met"] = (
|
|
65
|
+
spin_df["spin_start_sec_sclk"] + spin_df["spin_start_subsec_sclk"] / 1e6
|
|
52
66
|
)
|
|
53
67
|
|
|
54
68
|
return spin_df
|
|
@@ -71,18 +85,9 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
|
|
|
71
85
|
Returns
|
|
72
86
|
-------
|
|
73
87
|
spin_df : pandas.DataFrame
|
|
74
|
-
Spin table data
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* spin_start_sec
|
|
78
|
-
* spin_start_subsec
|
|
79
|
-
* spin_period_sec
|
|
80
|
-
* spin_period_valid
|
|
81
|
-
* spin_phase_valid
|
|
82
|
-
* spin_period_source
|
|
83
|
-
* thruster_firing
|
|
84
|
-
* spin_start_met
|
|
85
|
-
* sc_spin_phase
|
|
88
|
+
Spin table data interpolated for each queried MET time. In addition to
|
|
89
|
+
the columns output from :py:func:`get_spin_data`, the `sc_spin_phase`
|
|
90
|
+
column is added and is uniquely computed for each queried MET time.
|
|
86
91
|
"""
|
|
87
92
|
spin_df = get_spin_data()
|
|
88
93
|
|
|
@@ -95,9 +100,9 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
|
|
|
95
100
|
query_met_times = np.atleast_1d(query_met_times)
|
|
96
101
|
|
|
97
102
|
# Make sure input times are within the bounds of spin data
|
|
98
|
-
spin_df_start_time = spin_df["
|
|
103
|
+
spin_df_start_time = spin_df["spin_start_met"].values[0]
|
|
99
104
|
spin_df_end_time = (
|
|
100
|
-
spin_df["
|
|
105
|
+
spin_df["spin_start_met"].values[-1] + spin_df["spin_period_sec"].values[-1]
|
|
101
106
|
)
|
|
102
107
|
input_start_time = query_met_times.min()
|
|
103
108
|
input_end_time = query_met_times.max()
|
|
@@ -115,13 +120,13 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
|
|
|
115
120
|
# >>> np.searchsorted(df['a'], [0, 13, 15, 32, 70], side='right')
|
|
116
121
|
# array([1, 1, 2, 3, 5])
|
|
117
122
|
last_spin_indices = (
|
|
118
|
-
np.searchsorted(spin_df["
|
|
123
|
+
np.searchsorted(spin_df["spin_start_met"], query_met_times, side="right") - 1
|
|
119
124
|
)
|
|
120
125
|
# Generate a dataframe with one row per query time
|
|
121
126
|
out_df = spin_df.iloc[last_spin_indices]
|
|
122
127
|
|
|
123
128
|
# Calculate spin phase
|
|
124
|
-
spin_phases = (query_met_times - out_df["
|
|
129
|
+
spin_phases = (query_met_times - out_df["spin_start_met"].values) / out_df[
|
|
125
130
|
"spin_period_sec"
|
|
126
131
|
].values
|
|
127
132
|
|
|
@@ -185,7 +190,7 @@ def get_spacecraft_spin_phase(
|
|
|
185
190
|
Get the spacecraft spin phase for the input query times.
|
|
186
191
|
|
|
187
192
|
Formula to calculate spin phase:
|
|
188
|
-
spin_phase = (query_met_times -
|
|
193
|
+
spin_phase = (query_met_times - spin_start_met) / spin_period_sec
|
|
189
194
|
|
|
190
195
|
Parameters
|
|
191
196
|
----------
|
imap_processing/spice/time.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
4
|
from collections.abc import Collection, Iterable
|
|
5
|
+
from datetime import datetime
|
|
5
6
|
from typing import Union
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
@@ -297,3 +298,26 @@ def et_to_utc(
|
|
|
297
298
|
UTC time(s).
|
|
298
299
|
"""
|
|
299
300
|
return spiceypy.et2utc(et, format_str, precision, utclen)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def epoch_to_doy(epoch: np.ndarray) -> npt.NDArray:
|
|
304
|
+
"""
|
|
305
|
+
Convert epoch times to day of year (1-365/366).
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
epoch : xarray.DataArray
|
|
310
|
+
Time, number of nanoseconds since J2000 with leap seconds included.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
day_of_year : numpy.ndarray
|
|
315
|
+
Day of year (1-365/366) for each epoch value.
|
|
316
|
+
"""
|
|
317
|
+
et = ttj2000ns_to_et(epoch.data)
|
|
318
|
+
# Get UTC time strings in ISO calendar format
|
|
319
|
+
time_strings = et_to_utc(et, "ISOC")
|
|
320
|
+
# Extract DOY from datetime
|
|
321
|
+
return np.array(
|
|
322
|
+
[datetime.fromisoformat(date).timetuple().tm_yday for date in time_strings]
|
|
323
|
+
)
|
|
@@ -72,8 +72,7 @@ def filter_good_data(full_sweep_sci: xr.Dataset) -> npt.NDArray:
|
|
|
72
72
|
f"{full_sweep_sci['sweep_table'].data[bad_cycle_indices]}"
|
|
73
73
|
)
|
|
74
74
|
logger.debug(
|
|
75
|
-
"Plan ID should be same: "
|
|
76
|
-
f"{full_sweep_sci['plan_id'].data[bad_cycle_indices]}"
|
|
75
|
+
f"Plan ID should be same: {full_sweep_sci['plan_id'].data[bad_cycle_indices]}"
|
|
77
76
|
)
|
|
78
77
|
logger.debug(
|
|
79
78
|
f"Mode Id should be 3(HVSCI): {full_sweep_sci['mode'].data[bad_cycle_indices]}"
|
|
@@ -426,7 +425,7 @@ def process_sweep_data(full_sweep_sci: xr.Dataset, cem_prefix: str) -> xr.Datase
|
|
|
426
425
|
|
|
427
426
|
|
|
428
427
|
def process_swapi_science(
|
|
429
|
-
sci_dataset: xr.Dataset, hk_dataset: xr.Dataset
|
|
428
|
+
sci_dataset: xr.Dataset, hk_dataset: xr.Dataset
|
|
430
429
|
) -> xr.Dataset:
|
|
431
430
|
"""
|
|
432
431
|
Will process SWAPI science data and create CDF file.
|
|
@@ -437,8 +436,6 @@ def process_swapi_science(
|
|
|
437
436
|
L0 data.
|
|
438
437
|
hk_dataset : xarray.Dataset
|
|
439
438
|
Housekeeping data.
|
|
440
|
-
data_version : str
|
|
441
|
-
Version of the data product being created.
|
|
442
439
|
|
|
443
440
|
Returns
|
|
444
441
|
-------
|
|
@@ -586,8 +583,6 @@ def process_swapi_science(
|
|
|
586
583
|
)
|
|
587
584
|
|
|
588
585
|
# Add other global attributes
|
|
589
|
-
# TODO: add others like below once add_global_attribute is fixed
|
|
590
|
-
cdf_manager.add_global_attribute("Data_version", data_version)
|
|
591
586
|
l1_global_attrs = cdf_manager.get_global_attributes("imap_swapi_l1_sci")
|
|
592
587
|
l1_global_attrs["Apid"] = f"{sci_dataset['pkt_apid'].data[0]}"
|
|
593
588
|
|
|
@@ -632,6 +627,18 @@ def process_swapi_science(
|
|
|
632
627
|
dims=["epoch"],
|
|
633
628
|
attrs=cdf_manager.get_variable_attributes("plan_id"),
|
|
634
629
|
)
|
|
630
|
+
# Add ESA_LVL5 for L2 and L3 purposes.
|
|
631
|
+
# We need to store ESA_LVL5 at SEQ_NUMBER==11
|
|
632
|
+
# which is 71 energy step's ESA_LVL5 value. ESA_LVL5 gets
|
|
633
|
+
# updated every 6th step. This is used in L2 to calculate last 9 fine
|
|
634
|
+
# energy steps.
|
|
635
|
+
dataset["esa_lvl5"] = xr.DataArray(
|
|
636
|
+
good_sweep_sci["esa_lvl5"].data.reshape(total_full_sweeps, 12)[:, 11],
|
|
637
|
+
name="esa_lvl5",
|
|
638
|
+
dims=["epoch"],
|
|
639
|
+
attrs=cdf_manager.get_variable_attributes("esa_lvl5"),
|
|
640
|
+
)
|
|
641
|
+
|
|
635
642
|
# Add these additional housekeeping support data
|
|
636
643
|
# SWP_HK.LUT_CHOICE - Which LUT is in use
|
|
637
644
|
# SWP_HK.FPGA_TYPE - Type number of the FPGA
|
|
@@ -703,7 +710,7 @@ def process_swapi_science(
|
|
|
703
710
|
return dataset
|
|
704
711
|
|
|
705
712
|
|
|
706
|
-
def swapi_l1(dependencies: list
|
|
713
|
+
def swapi_l1(dependencies: list) -> xr.Dataset:
|
|
707
714
|
"""
|
|
708
715
|
Will process SWAPI level 0 data to level 1.
|
|
709
716
|
|
|
@@ -711,8 +718,6 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
|
|
|
711
718
|
----------
|
|
712
719
|
dependencies : list
|
|
713
720
|
Input dependencies needed for L1 processing.
|
|
714
|
-
data_version : str
|
|
715
|
-
Version of the data product being created.
|
|
716
721
|
|
|
717
722
|
Returns
|
|
718
723
|
-------
|
|
@@ -744,7 +749,7 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
|
|
|
744
749
|
):
|
|
745
750
|
# process science data
|
|
746
751
|
sci_dataset = process_swapi_science(
|
|
747
|
-
l0_unpacked_dict[SWAPIAPID.SWP_SCI], l1_hk_ds
|
|
752
|
+
l0_unpacked_dict[SWAPIAPID.SWP_SCI], l1_hk_ds
|
|
748
753
|
)
|
|
749
754
|
processed_data.append(sci_dataset)
|
|
750
755
|
|
|
@@ -753,7 +758,6 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
|
|
|
753
758
|
# Add HK datalevel attrs
|
|
754
759
|
imap_attrs = ImapCdfAttributes()
|
|
755
760
|
imap_attrs.add_instrument_global_attrs("swapi")
|
|
756
|
-
imap_attrs.add_global_attribute("Data_version", data_version)
|
|
757
761
|
imap_attrs.add_instrument_variable_attrs(instrument="swapi", level=None)
|
|
758
762
|
hk_ds.attrs.update(imap_attrs.get_global_attributes("imap_swapi_l1_hk"))
|
|
759
763
|
hk_common_attrs = imap_attrs.get_variable_attributes("hk_attrs")
|