imap-processing 0.7.0__py3-none-any.whl → 0.9.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 -1
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/excel_to_xtce.py +36 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
- imap_processing/cdf/config/imap_hi_variable_attrs.yaml +136 -9
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
- imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
- imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +14 -7
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +577 -235
- imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
- imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
- imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +137 -79
- imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
- imap_processing/cdf/imap_cdf_manager.py +31 -27
- imap_processing/cdf/utils.py +3 -5
- imap_processing/cli.py +25 -14
- imap_processing/codice/codice_l1a.py +153 -63
- imap_processing/codice/constants.py +10 -10
- imap_processing/codice/decompress.py +10 -11
- imap_processing/codice/utils.py +1 -0
- imap_processing/glows/l1a/glows_l1a.py +1 -2
- imap_processing/glows/l1b/glows_l1b.py +3 -3
- imap_processing/glows/l1b/glows_l1b_data.py +59 -37
- imap_processing/glows/l2/glows_l2_data.py +123 -0
- imap_processing/hi/l1a/hi_l1a.py +4 -4
- imap_processing/hi/l1a/histogram.py +107 -109
- imap_processing/hi/l1a/science_direct_event.py +92 -225
- imap_processing/hi/l1b/hi_l1b.py +85 -11
- imap_processing/hi/l1c/hi_l1c.py +23 -1
- imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
- imap_processing/hi/utils.py +1 -1
- imap_processing/hit/hit_utils.py +221 -0
- imap_processing/hit/l0/constants.py +118 -0
- imap_processing/hit/l0/decom_hit.py +100 -156
- imap_processing/hit/l1a/hit_l1a.py +170 -184
- imap_processing/hit/l1b/hit_l1b.py +33 -153
- imap_processing/ialirt/l0/process_codicelo.py +153 -0
- imap_processing/ialirt/l0/process_hit.py +5 -5
- imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
- imap_processing/ialirt/process_ephemeris.py +212 -0
- imap_processing/idex/idex_l1a.py +65 -84
- imap_processing/idex/idex_l1b.py +192 -0
- imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
- imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
- imap_processing/lo/l0/decompression_tables/decompression_tables.py +17 -1
- imap_processing/lo/l0/lo_science.py +45 -13
- imap_processing/lo/l1a/lo_l1a.py +76 -8
- imap_processing/lo/packet_definitions/lo_xtce.xml +8344 -1849
- imap_processing/mag/l0/decom_mag.py +4 -3
- imap_processing/mag/l1a/mag_l1a.py +12 -13
- imap_processing/mag/l1a/mag_l1a_data.py +1 -2
- imap_processing/mag/l1b/mag_l1b.py +90 -7
- imap_processing/spice/geometry.py +156 -16
- imap_processing/spice/time.py +144 -2
- imap_processing/swapi/l1/swapi_l1.py +4 -4
- imap_processing/swapi/l2/swapi_l2.py +1 -1
- imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
- imap_processing/swe/l1b/swe_l1b_science.py +8 -8
- imap_processing/swe/l2/swe_l2.py +134 -17
- imap_processing/tests/ccsds/test_data/expected_output.xml +2 -1
- imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
- imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
- imap_processing/tests/codice/conftest.py +1 -17
- imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
- imap_processing/tests/codice/test_codice_l0.py +8 -2
- imap_processing/tests/codice/test_codice_l1a.py +127 -107
- imap_processing/tests/codice/test_codice_l1b.py +1 -0
- imap_processing/tests/codice/test_decompress.py +7 -7
- imap_processing/tests/conftest.py +100 -58
- imap_processing/tests/glows/conftest.py +6 -0
- imap_processing/tests/glows/test_glows_l1b.py +9 -9
- imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
- imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
- imap_processing/tests/hi/test_hi_l1b.py +73 -3
- imap_processing/tests/hi/test_hi_l1c.py +10 -2
- imap_processing/tests/hi/test_l1a.py +31 -58
- imap_processing/tests/hi/test_science_direct_event.py +58 -0
- imap_processing/tests/hi/test_utils.py +4 -3
- imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
- imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +95 -36
- imap_processing/tests/hit/test_hit_l1a.py +299 -179
- imap_processing/tests/hit/test_hit_l1b.py +231 -24
- imap_processing/tests/hit/test_hit_utils.py +218 -0
- imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
- imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
- imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
- imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
- imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
- imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
- imap_processing/tests/idex/conftest.py +2 -2
- imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
- imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
- imap_processing/tests/idex/test_idex_l0.py +4 -4
- imap_processing/tests/idex/test_idex_l1a.py +8 -2
- imap_processing/tests/idex/test_idex_l1b.py +126 -0
- imap_processing/tests/lo/test_lo_l1a.py +7 -16
- imap_processing/tests/lo/test_lo_science.py +69 -5
- imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
- imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
- imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
- imap_processing/tests/mag/test_mag_l1b.py +97 -7
- imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
- imap_processing/tests/spice/test_geometry.py +115 -9
- imap_processing/tests/spice/test_time.py +135 -6
- imap_processing/tests/swapi/test_swapi_decom.py +75 -69
- imap_processing/tests/swapi/test_swapi_l1.py +4 -4
- imap_processing/tests/swe/conftest.py +33 -0
- imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
- imap_processing/tests/swe/test_swe_l1b.py +29 -8
- imap_processing/tests/swe/test_swe_l2.py +64 -8
- imap_processing/tests/test_utils.py +2 -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/l1/dps_exposure_helio_45_E12.cdf +0 -0
- imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
- imap_processing/tests/ultra/unit/test_de.py +113 -0
- imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +55 -35
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +10 -68
- imap_processing/ultra/constants.py +12 -3
- imap_processing/ultra/l1b/de.py +168 -30
- imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
- imap_processing/ultra/l1b/ultra_l1b_extended.py +46 -80
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +60 -144
- imap_processing/ultra/utils/spatial_utils.py +221 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +15 -14
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +142 -139
- imap_processing/cdf/cdf_attribute_manager.py +0 -322
- imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/hi/l0/decom_hi.py +0 -24
- imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
- imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
- imap_processing/hit/l0/data_classes/science_packet.py +0 -259
- imap_processing/hit/l0/utils/hit_base.py +0 -57
- imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/hi/test_decom.py +0 -55
- imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
- imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
- imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
- /imap_processing/{hi/l0/__init__.py → tests/glows/test_glows_l2_data.py} +0 -0
- /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
|
Binary file
|
|
@@ -5,25 +5,50 @@ import pytest
|
|
|
5
5
|
import xarray as xr
|
|
6
6
|
|
|
7
7
|
from imap_processing.cdf.utils import load_cdf, write_cdf
|
|
8
|
-
from imap_processing.mag.l1b.mag_l1b import
|
|
8
|
+
from imap_processing.mag.l1b.mag_l1b import (
|
|
9
|
+
calibrate_vector,
|
|
10
|
+
mag_l1b,
|
|
11
|
+
mag_l1b_processing,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
@pytest.fixture(scope="module")
|
|
12
16
|
def mag_l1a_dataset():
|
|
13
17
|
epoch = xr.DataArray(np.arange(20), name="epoch", dims=["epoch"])
|
|
14
18
|
direction = xr.DataArray(np.arange(4), name="direction", dims=["direction"])
|
|
19
|
+
compression = xr.DataArray(np.arange(2), name="compression", dims=["compression"])
|
|
20
|
+
|
|
21
|
+
direction_label = xr.DataArray(
|
|
22
|
+
direction.values.astype(str),
|
|
23
|
+
name="direction_label",
|
|
24
|
+
dims=["direction_label"],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
compression_label = xr.DataArray(
|
|
28
|
+
compression.values.astype(str),
|
|
29
|
+
name="compression_label",
|
|
30
|
+
dims=["compression_label"],
|
|
31
|
+
)
|
|
32
|
+
|
|
15
33
|
vectors = xr.DataArray(
|
|
16
34
|
np.zeros((20, 4)),
|
|
17
35
|
dims=["epoch", "direction"],
|
|
18
36
|
coords={"epoch": epoch, "direction": direction},
|
|
19
37
|
)
|
|
38
|
+
compression_flags = xr.DataArray(
|
|
39
|
+
np.zeros((20, 2), dtype=np.int8), dims=["epoch", "compression"]
|
|
40
|
+
)
|
|
20
41
|
|
|
21
42
|
vectors[0, :] = np.array([1, 1, 1, 0])
|
|
22
43
|
|
|
23
44
|
output_dataset = xr.Dataset(
|
|
24
|
-
coords={"epoch": epoch, "direction": direction},
|
|
45
|
+
coords={"epoch": epoch, "direction": direction, "compression": compression},
|
|
25
46
|
)
|
|
26
47
|
output_dataset["vectors"] = vectors
|
|
48
|
+
output_dataset["compression_flags"] = compression_flags
|
|
49
|
+
output_dataset["direction_label"] = direction_label
|
|
50
|
+
output_dataset["compression_label"] = compression_label
|
|
51
|
+
output_dataset.attrs["Logical_source"] = ["imap_mag_l1a_norm-mago"]
|
|
27
52
|
|
|
28
53
|
return output_dataset
|
|
29
54
|
|
|
@@ -32,9 +57,8 @@ def test_mag_processing(mag_l1a_dataset):
|
|
|
32
57
|
mag_l1a_dataset.attrs["Logical_source"] = ["imap_mag_l1a_norm-mago"]
|
|
33
58
|
|
|
34
59
|
mag_l1b = mag_l1b_processing(mag_l1a_dataset)
|
|
35
|
-
|
|
36
60
|
np.testing.assert_allclose(
|
|
37
|
-
mag_l1b["vectors"][0].values, [2.
|
|
61
|
+
mag_l1b["vectors"][0].values, [2.2972, 2.2415, 2.2381, 0], atol=1e-4
|
|
38
62
|
)
|
|
39
63
|
np.testing.assert_allclose(mag_l1b["vectors"][1].values, [0, 0, 0, 0])
|
|
40
64
|
|
|
@@ -45,7 +69,7 @@ def test_mag_processing(mag_l1a_dataset):
|
|
|
45
69
|
mag_l1b = mag_l1b_processing(mag_l1a_dataset)
|
|
46
70
|
|
|
47
71
|
np.testing.assert_allclose(
|
|
48
|
-
mag_l1b["vectors"][0].values, [2.
|
|
72
|
+
mag_l1b["vectors"][0].values, [2.27538, 2.23416, 2.23682, 0], atol=1e-5
|
|
49
73
|
)
|
|
50
74
|
np.testing.assert_allclose(mag_l1b["vectors"][1].values, [0, 0, 0, 0])
|
|
51
75
|
|
|
@@ -66,13 +90,79 @@ def test_mag_attributes(mag_l1a_dataset):
|
|
|
66
90
|
assert output.attrs["Data_level"] == "L1B"
|
|
67
91
|
|
|
68
92
|
|
|
69
|
-
@pytest.mark.skip(reason="Epoch variable data need to be monotonically increasing")
|
|
70
93
|
def test_cdf_output():
|
|
71
94
|
l1a_cdf = load_cdf(
|
|
72
|
-
Path(__file__).parent / "
|
|
95
|
+
Path(__file__).parent / "imap_mag_l1a_norm-magi_20251017_v001.cdf"
|
|
73
96
|
)
|
|
74
97
|
l1b_dataset = mag_l1b(l1a_cdf, "v001")
|
|
75
98
|
|
|
76
99
|
output_path = write_cdf(l1b_dataset)
|
|
77
100
|
|
|
78
101
|
assert Path.exists(output_path)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_mag_compression_scale(mag_l1a_dataset):
|
|
105
|
+
test_calibration = np.array(
|
|
106
|
+
[
|
|
107
|
+
[2.2972202, 0.0, 0.0],
|
|
108
|
+
[0.00348625, 2.23802879, 0.0],
|
|
109
|
+
[-0.00250788, -0.00888437, 2.24950008],
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
mag_l1a_dataset["vectors"][0, :] = np.array([1, 1, 1, 0])
|
|
113
|
+
mag_l1a_dataset["vectors"][1, :] = np.array([1, 1, 1, 0])
|
|
114
|
+
mag_l1a_dataset["vectors"][2, :] = np.array([1, 1, 1, 0])
|
|
115
|
+
mag_l1a_dataset["vectors"][3, :] = np.array([1, 1, 1, 0])
|
|
116
|
+
|
|
117
|
+
mag_l1a_dataset["compression_flags"][0, :] = np.array([1, 16], dtype=np.int8)
|
|
118
|
+
mag_l1a_dataset["compression_flags"][1, :] = np.array([0, 0], dtype=np.int8)
|
|
119
|
+
mag_l1a_dataset["compression_flags"][2, :] = np.array([1, 18], dtype=np.int8)
|
|
120
|
+
mag_l1a_dataset["compression_flags"][3, :] = np.array([1, 14], dtype=np.int8)
|
|
121
|
+
|
|
122
|
+
mag_l1a_dataset.attrs["Logical_source"] = ["imap_mag_l1a_norm-mago"]
|
|
123
|
+
output = mag_l1b(mag_l1a_dataset, "v001")
|
|
124
|
+
|
|
125
|
+
calibrated_vectors = np.matmul(test_calibration, np.array([1, 1, 1]))
|
|
126
|
+
# 16 bit width is the standard
|
|
127
|
+
assert np.allclose(output["vectors"].data[0][:3], calibrated_vectors)
|
|
128
|
+
# uncompressed data is uncorrected
|
|
129
|
+
assert np.allclose(output["vectors"].data[1][:3], calibrated_vectors)
|
|
130
|
+
|
|
131
|
+
# width of 18 should be multiplied by 1/4
|
|
132
|
+
scaled_vectors = calibrated_vectors * 1 / 4
|
|
133
|
+
# should be corrected
|
|
134
|
+
assert np.allclose(output["vectors"].data[2][:3], scaled_vectors)
|
|
135
|
+
|
|
136
|
+
# width of 14 should be multiplied by 4
|
|
137
|
+
scaled_vectors = calibrated_vectors * 4
|
|
138
|
+
assert np.allclose(output["vectors"].data[3][:3], scaled_vectors)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_calibrate_vector():
|
|
142
|
+
# from MFOTOURFO
|
|
143
|
+
cal_array = np.array(
|
|
144
|
+
[
|
|
145
|
+
[
|
|
146
|
+
[2.29722020e00, 7.38200160e-02, 1.88479865e-02, 4.59777333e-03],
|
|
147
|
+
[0.00000000e00, 0.00000000e00, 0.00000000e00, 0.00000000e00],
|
|
148
|
+
[0.00000000e00, 0.00000000e00, 0.00000000e00, 0.00000000e00],
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
[3.48624576e-03, 1.09224000e-04, 3.26118600e-05, 5.02830000e-06],
|
|
152
|
+
[2.23802879e00, 7.23781440e-02, 1.84842873e-02, 4.50744060e-03],
|
|
153
|
+
[0.00000000e00, 0.00000000e00, 0.00000000e00, 0.00000000e00],
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
[-2.50787532e-03, -8.33760000e-05, -2.71240200e-05, 2.50509000e-06],
|
|
157
|
+
[-8.88437262e-03, -2.84256000e-04, -7.41600000e-05, -2.29399200e-05],
|
|
158
|
+
[2.24950008e00, 7.23836160e-02, 1.84847323e-02, 4.50945192e-03],
|
|
159
|
+
],
|
|
160
|
+
]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
calibration_matrix = xr.DataArray(cal_array)
|
|
164
|
+
|
|
165
|
+
cal_vector = calibrate_vector(np.array([1.0, 1.0, 1.0, 0]), calibration_matrix)
|
|
166
|
+
expected_vector = np.array([2.2972, 2.2415, 2.2381, 0])
|
|
167
|
+
|
|
168
|
+
assert np.allclose(cal_vector, expected_vector, atol=1e-4)
|
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
{SPICE_TEST_DATA_PATH}/imap_spk_demo.bsp
|
|
4
4
|
{SPICE_TEST_DATA_PATH}/sim_1yr_imap_attitude.bc
|
|
5
5
|
{SPICE_TEST_DATA_PATH}/imap_wkcp.tf
|
|
6
|
-
{SPICE_TEST_DATA_PATH}/de440s.bsp
|
|
6
|
+
{SPICE_TEST_DATA_PATH}/de440s.bsp
|
|
7
|
+
{SPICE_TEST_DATA_PATH}/imap_science_0001.tf
|
|
8
|
+
{SPICE_TEST_DATA_PATH}/sim_1yr_imap_pointing_frame.bc
|
|
@@ -8,6 +8,8 @@ import spiceypy as spice
|
|
|
8
8
|
from imap_processing.spice.geometry import (
|
|
9
9
|
SpiceBody,
|
|
10
10
|
SpiceFrame,
|
|
11
|
+
basis_vectors,
|
|
12
|
+
cartesian_to_spherical,
|
|
11
13
|
frame_transform,
|
|
12
14
|
get_instrument_spin_phase,
|
|
13
15
|
get_rotation_matrix,
|
|
@@ -16,7 +18,9 @@ from imap_processing.spice.geometry import (
|
|
|
16
18
|
get_spin_data,
|
|
17
19
|
imap_state,
|
|
18
20
|
instrument_pointing,
|
|
21
|
+
spherical_to_cartesian,
|
|
19
22
|
)
|
|
23
|
+
from imap_processing.spice.kernels import ensure_spice
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
@pytest.mark.parametrize(
|
|
@@ -99,10 +103,10 @@ def test_get_spacecraft_spin_phase_value_error(query_met_times, fake_spin_data):
|
|
|
99
103
|
_ = get_spacecraft_spin_phase(query_met_times)
|
|
100
104
|
|
|
101
105
|
|
|
102
|
-
@pytest.mark.usefixtures("
|
|
103
|
-
def test_get_spin_data():
|
|
106
|
+
@pytest.mark.usefixtures("use_fake_spin_data_for_time")
|
|
107
|
+
def test_get_spin_data(use_fake_spin_data_for_time):
|
|
104
108
|
"""Test get_spin_data() with generated spin data."""
|
|
105
|
-
|
|
109
|
+
use_fake_spin_data_for_time(453051323.0 - 56120)
|
|
106
110
|
spin_data = get_spin_data()
|
|
107
111
|
|
|
108
112
|
(
|
|
@@ -200,6 +204,19 @@ def test_get_spacecraft_to_instrument_spin_phase_offset(instrument, expected_off
|
|
|
200
204
|
SpiceFrame.IMAP_SPACECRAFT,
|
|
201
205
|
SpiceFrame.IMAP_DPS,
|
|
202
206
|
),
|
|
207
|
+
# single et, multiple position vectors
|
|
208
|
+
(
|
|
209
|
+
["2025-04-30T12:00:00.000"],
|
|
210
|
+
np.array(
|
|
211
|
+
[
|
|
212
|
+
[1, 0, 0],
|
|
213
|
+
[0, 1, 0],
|
|
214
|
+
[0, 0, 1],
|
|
215
|
+
]
|
|
216
|
+
),
|
|
217
|
+
SpiceFrame.IMAP_SPACECRAFT,
|
|
218
|
+
SpiceFrame.IMAP_DPS,
|
|
219
|
+
),
|
|
203
220
|
],
|
|
204
221
|
)
|
|
205
222
|
def test_frame_transform(et_strings, position, from_frame, to_frame, furnish_kernels):
|
|
@@ -219,11 +236,24 @@ def test_frame_transform(et_strings, position, from_frame, to_frame, furnish_ker
|
|
|
219
236
|
et = np.array([spice.utc2et(et_str) for et_str in et_strings])
|
|
220
237
|
et_arg = et[0] if len(et) == 1 else et
|
|
221
238
|
result = frame_transform(et_arg, position, from_frame, to_frame)
|
|
222
|
-
# check the result shape before modifying for value checking
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
239
|
+
# check the result shape before modifying for value checking.
|
|
240
|
+
# There are 3 cases to consider:
|
|
241
|
+
|
|
242
|
+
# 1 event time, multiple position vectors:
|
|
243
|
+
if len(et) == 1 and position.ndim > 1:
|
|
244
|
+
assert result.shape == position.shape
|
|
245
|
+
# multiple event times, single position vector:
|
|
246
|
+
elif len(et) > 1 and position.ndim == 1:
|
|
247
|
+
assert result.shape == (len(et), 3)
|
|
248
|
+
# multiple event times, multiple position vectors (same number of each)
|
|
249
|
+
elif len(et) > 1 and position.ndim > 1:
|
|
250
|
+
assert result.shape == (len(et), 3)
|
|
251
|
+
|
|
252
|
+
# compare against pure SPICE calculation.
|
|
253
|
+
# If the result is a single position vector, broadcast it to first.
|
|
254
|
+
if position.ndim == 1:
|
|
255
|
+
position = np.broadcast_to(position, (len(et), 3))
|
|
256
|
+
result = np.broadcast_to(result, (len(et), 3))
|
|
227
257
|
for spice_et, spice_position, test_result in zip(et, position, result):
|
|
228
258
|
rotation_matrix = spice.pxform(from_frame.name, to_frame.name, spice_et)
|
|
229
259
|
spice_result = spice.mxv(rotation_matrix, spice_position)
|
|
@@ -250,7 +280,7 @@ def test_frame_transform_exceptions():
|
|
|
250
280
|
match="Mismatch in number of position vectors and Ephemeris times provided.",
|
|
251
281
|
):
|
|
252
282
|
frame_transform(
|
|
253
|
-
|
|
283
|
+
[1, 2],
|
|
254
284
|
np.arange(9).reshape((3, 3)),
|
|
255
285
|
SpiceFrame.ECLIPJ2000,
|
|
256
286
|
SpiceFrame.IMAP_HIT,
|
|
@@ -306,3 +336,79 @@ def test_instrument_pointing(furnish_kernels):
|
|
|
306
336
|
et, SpiceFrame.IMAP_HI_90, SpiceFrame.ECLIPJ2000, cartesian=True
|
|
307
337
|
)
|
|
308
338
|
assert ins_pointing.shape == (3, 3)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@pytest.mark.external_kernel()
|
|
342
|
+
@pytest.mark.use_test_metakernel("imap_ena_sim_metakernel.template")
|
|
343
|
+
def test_basis_vectors():
|
|
344
|
+
"""Test coverage for basis_vectors()."""
|
|
345
|
+
# This call to SPICE needs to be wrapped with `ensure_spice` so that kernels
|
|
346
|
+
# get furnished automatically
|
|
347
|
+
et = ensure_spice(spice.utc2et)("2025-09-30T12:00:00.000")
|
|
348
|
+
# test input of float
|
|
349
|
+
sc_axes = basis_vectors(et, SpiceFrame.IMAP_SPACECRAFT, SpiceFrame.IMAP_SPACECRAFT)
|
|
350
|
+
np.testing.assert_array_equal(sc_axes, np.eye(3))
|
|
351
|
+
# test array of et input
|
|
352
|
+
et_array = np.arange(10) + et
|
|
353
|
+
sc_axes = basis_vectors(et_array, SpiceFrame.IMAP_SPACECRAFT, SpiceFrame.ECLIPJ2000)
|
|
354
|
+
assert sc_axes.shape == (10, 3, 3)
|
|
355
|
+
# Verify that for each time, the basis vectors are correct
|
|
356
|
+
for et, basis_matrix in zip(et_array, sc_axes):
|
|
357
|
+
np.testing.assert_array_equal(
|
|
358
|
+
basis_matrix,
|
|
359
|
+
frame_transform(
|
|
360
|
+
et * np.ones(3),
|
|
361
|
+
np.eye(3),
|
|
362
|
+
SpiceFrame.IMAP_SPACECRAFT,
|
|
363
|
+
SpiceFrame.ECLIPJ2000,
|
|
364
|
+
),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def test_cartesian_to_spherical():
|
|
369
|
+
"""Tests cartesian_to_spherical function."""
|
|
370
|
+
|
|
371
|
+
step = 0.05
|
|
372
|
+
x = np.arange(-1, 1 + step, step)
|
|
373
|
+
y = np.arange(-1, 1 + step, step)
|
|
374
|
+
z = np.arange(-1, 1 + step, step)
|
|
375
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
376
|
+
|
|
377
|
+
cartesian_points = np.stack((x.ravel(), y.ravel(), z.ravel()), axis=-1)
|
|
378
|
+
|
|
379
|
+
for point in cartesian_points:
|
|
380
|
+
r, az, el = cartesian_to_spherical(point)
|
|
381
|
+
r_spice, colat_spice, slong_spice = spice.recsph(point)
|
|
382
|
+
|
|
383
|
+
# Convert SPICE co-latitude to elevation
|
|
384
|
+
el_spice = 90 - np.degrees(colat_spice)
|
|
385
|
+
az_spice = np.degrees(slong_spice)
|
|
386
|
+
|
|
387
|
+
# Normalize azimuth to [0, 360]
|
|
388
|
+
az_spice = az_spice % 360
|
|
389
|
+
|
|
390
|
+
np.testing.assert_allclose(r, r_spice, atol=1e-5)
|
|
391
|
+
np.testing.assert_allclose(az, az_spice, atol=1e-5)
|
|
392
|
+
np.testing.assert_allclose(el, el_spice, atol=1e-5)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def test_spherical_to_cartesian():
|
|
396
|
+
"""Tests spherical_to_cartesian function."""
|
|
397
|
+
|
|
398
|
+
azimuth = np.linspace(0, 2 * np.pi, 50)
|
|
399
|
+
elevation = np.linspace(-np.pi / 2, np.pi / 2, 50)
|
|
400
|
+
theta, elev = np.meshgrid(azimuth, elevation)
|
|
401
|
+
r = 1.0
|
|
402
|
+
|
|
403
|
+
spherical_points = np.stack(
|
|
404
|
+
(r * np.ones_like(theta).ravel(), theta.ravel(), elev.ravel()), axis=-1
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Convert elevation to colatitude for SPICE
|
|
408
|
+
colat = np.pi / 2 - spherical_points[:, 2]
|
|
409
|
+
|
|
410
|
+
for i in range(len(colat)):
|
|
411
|
+
cartesian_coords = spherical_to_cartesian(np.array([spherical_points[i]]))
|
|
412
|
+
spice_coords = spice.sphrec(r, colat[i], spherical_points[i, 1])
|
|
413
|
+
|
|
414
|
+
np.testing.assert_allclose(cartesian_coords[0], spice_coords, atol=1e-5)
|
|
@@ -2,22 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pytest
|
|
5
|
-
import spiceypy
|
|
5
|
+
import spiceypy
|
|
6
6
|
|
|
7
7
|
from imap_processing.spice import IMAP_SC_ID
|
|
8
|
-
from imap_processing.spice.time import
|
|
8
|
+
from imap_processing.spice.time import (
|
|
9
|
+
TICK_DURATION,
|
|
10
|
+
_sct2e_wrapper,
|
|
11
|
+
et_to_utc,
|
|
12
|
+
j2000ns_to_j2000s,
|
|
13
|
+
met_to_datetime64,
|
|
14
|
+
met_to_j2000ns,
|
|
15
|
+
met_to_sclkticks,
|
|
16
|
+
met_to_utc,
|
|
17
|
+
str_to_et,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.parametrize("met", [1, np.arange(10)])
|
|
22
|
+
def test_met_to_sclkticks(met):
|
|
23
|
+
"""Test coverage for met_to_sclkticks."""
|
|
24
|
+
# Tick duration is 20us as specified in imap_sclk_0000.tsc
|
|
25
|
+
expected = met * 1 / 20e-6
|
|
26
|
+
ticks = met_to_sclkticks(met)
|
|
27
|
+
np.testing.assert_array_equal(ticks, expected)
|
|
9
28
|
|
|
10
29
|
|
|
11
30
|
def test_met_to_j2000ns(furnish_time_kernels):
|
|
12
31
|
"""Test coverage for met_to_j2000ns function."""
|
|
13
32
|
utc = "2026-01-01T00:00:00.125"
|
|
14
|
-
et =
|
|
15
|
-
sclk_str =
|
|
33
|
+
et = spiceypy.str2et(utc)
|
|
34
|
+
sclk_str = spiceypy.sce2s(IMAP_SC_ID, et)
|
|
16
35
|
seconds, ticks = sclk_str.split("/")[1].split(":")
|
|
17
36
|
# There is some floating point error calculating tick duration from 1 clock
|
|
18
37
|
# tick so average over many clock ticks for better accuracy
|
|
19
38
|
spice_tick_duration = (
|
|
20
|
-
|
|
39
|
+
spiceypy.sct2e(IMAP_SC_ID, 1e12) - spiceypy.sct2e(IMAP_SC_ID, 0)
|
|
21
40
|
) / 1e12
|
|
22
41
|
met = float(seconds) + float(ticks) * spice_tick_duration
|
|
23
42
|
j2000ns = met_to_j2000ns(met)
|
|
@@ -30,7 +49,7 @@ def test_j2000ns_to_j2000s(furnish_time_kernels):
|
|
|
30
49
|
# Use spice to come up with reasonable J2000 values
|
|
31
50
|
utc = "2025-09-23T00:00:00.000"
|
|
32
51
|
# Test single value input
|
|
33
|
-
et =
|
|
52
|
+
et = spiceypy.str2et(utc)
|
|
34
53
|
epoch = int(et * 1e9)
|
|
35
54
|
j2000s = j2000ns_to_j2000s(epoch)
|
|
36
55
|
assert j2000s == et
|
|
@@ -42,6 +61,61 @@ def test_j2000ns_to_j2000s(furnish_time_kernels):
|
|
|
42
61
|
)
|
|
43
62
|
|
|
44
63
|
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"expected_utc, precision",
|
|
66
|
+
[
|
|
67
|
+
("2024-01-01T00:00:00.000", 3),
|
|
68
|
+
(
|
|
69
|
+
[
|
|
70
|
+
"2024-01-01T00:00:00.000555",
|
|
71
|
+
"2025-09-23T00:00:00.000111",
|
|
72
|
+
"2040-11-14T10:23:48.156980",
|
|
73
|
+
],
|
|
74
|
+
6,
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
def test_met_to_utc(furnish_time_kernels, expected_utc, precision):
|
|
79
|
+
"""Test coverage for met_to_utc function."""
|
|
80
|
+
if isinstance(expected_utc, list):
|
|
81
|
+
et_arr = spiceypy.str2et(expected_utc)
|
|
82
|
+
sclk_ticks = np.array([spiceypy.sce2c(IMAP_SC_ID, et) for et in et_arr])
|
|
83
|
+
else:
|
|
84
|
+
et = spiceypy.str2et(expected_utc)
|
|
85
|
+
sclk_ticks = spiceypy.sce2c(IMAP_SC_ID, et)
|
|
86
|
+
met = sclk_ticks * TICK_DURATION
|
|
87
|
+
utc = met_to_utc(met, precision=precision)
|
|
88
|
+
np.testing.assert_array_equal(utc, expected_utc)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize(
|
|
92
|
+
"utc",
|
|
93
|
+
[
|
|
94
|
+
"2024-01-01T00:00:00.000",
|
|
95
|
+
[
|
|
96
|
+
"2024-01-01T00:00:00.000",
|
|
97
|
+
"2025-09-23T00:00:00.000",
|
|
98
|
+
"2040-11-14T10:23:48.15698",
|
|
99
|
+
],
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_met_to_datetime64(furnish_time_kernels, utc):
|
|
103
|
+
"""Test coverage for met_to_datetime64 function."""
|
|
104
|
+
if isinstance(utc, list):
|
|
105
|
+
expected_dt64 = np.array([np.datetime64(utc_str) for utc_str in utc])
|
|
106
|
+
et_arr = spiceypy.str2et(utc)
|
|
107
|
+
sclk_ticks = np.array([spiceypy.sce2c(IMAP_SC_ID, et) for et in et_arr])
|
|
108
|
+
else:
|
|
109
|
+
expected_dt64 = np.asarray(np.datetime64(utc))
|
|
110
|
+
et = spiceypy.str2et(utc)
|
|
111
|
+
sclk_ticks = spiceypy.sce2c(IMAP_SC_ID, et)
|
|
112
|
+
met = sclk_ticks * TICK_DURATION
|
|
113
|
+
dt64 = met_to_datetime64(met)
|
|
114
|
+
np.testing.assert_array_equal(
|
|
115
|
+
dt64.astype("datetime64[us]"), expected_dt64.astype("datetime64[us]")
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
45
119
|
@pytest.mark.parametrize("sclk_ticks", [0.0, np.arange(10)])
|
|
46
120
|
def test_sct2e_wrapper(sclk_ticks):
|
|
47
121
|
"""Test for `_sct2e_wrapper` function."""
|
|
@@ -50,3 +124,58 @@ def test_sct2e_wrapper(sclk_ticks):
|
|
|
50
124
|
assert isinstance(et, float)
|
|
51
125
|
else:
|
|
52
126
|
assert len(et) == len(sclk_ticks)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_str_to_et(furnish_time_kernels):
|
|
130
|
+
"""Test coverage for string to et conversion function."""
|
|
131
|
+
utc = "2017-07-14T19:46:00"
|
|
132
|
+
# Test single value input
|
|
133
|
+
expected_et = 553333629.1837274
|
|
134
|
+
actual_et = str_to_et(utc)
|
|
135
|
+
assert expected_et == actual_et
|
|
136
|
+
|
|
137
|
+
# Test list input
|
|
138
|
+
list_of_utc = [
|
|
139
|
+
"2017-08-14T19:46:00.000",
|
|
140
|
+
"2017-09-14T19:46:00.000",
|
|
141
|
+
"2017-10-14T19:46:00.000",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
expected_et_array = np.array(
|
|
145
|
+
(556012029.1829445, 558690429.1824446, 561282429.1823651)
|
|
146
|
+
)
|
|
147
|
+
actual_et_array = str_to_et(list_of_utc)
|
|
148
|
+
assert np.array_equal(expected_et_array, actual_et_array)
|
|
149
|
+
|
|
150
|
+
# Test array input
|
|
151
|
+
array_of_utc = np.array(
|
|
152
|
+
[
|
|
153
|
+
"2017-08-14T19:46:00.000",
|
|
154
|
+
"2017-09-14T19:46:00.000",
|
|
155
|
+
"2017-10-14T19:46:00.000",
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
actual_et_array = str_to_et(array_of_utc)
|
|
160
|
+
assert np.array_equal(expected_et_array, actual_et_array)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_et_to_utc(furnish_time_kernels):
|
|
164
|
+
"""Test coverage for et to utc conversion function."""
|
|
165
|
+
et = 553333629.1837274
|
|
166
|
+
# Test single value input
|
|
167
|
+
expected_utc = "2017-07-14T19:46:00.000"
|
|
168
|
+
actual_utc = et_to_utc(et)
|
|
169
|
+
assert expected_utc == actual_utc
|
|
170
|
+
|
|
171
|
+
# Test array input
|
|
172
|
+
array_of_et = np.array((556012029.1829445, 558690429.1824446, 561282429.1823651))
|
|
173
|
+
expected_utc_array = np.array(
|
|
174
|
+
(
|
|
175
|
+
"2017-08-14T19:46:00.000",
|
|
176
|
+
"2017-09-14T19:46:00.000",
|
|
177
|
+
"2017-10-14T19:46:00.000",
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
actual_utc_array = et_to_utc(array_of_et)
|
|
181
|
+
assert np.array_equal(expected_utc_array, actual_utc_array)
|