imap-processing 0.17.0__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imap-processing might be problematic. Click here for more details.
- imap_processing/_version.py +2 -2
- imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
- imap_processing/ccsds/excel_to_xtce.py +12 -0
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +312 -274
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +39 -28
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1048 -183
- imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
- imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
- imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
- imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
- imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +44 -44
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +77 -61
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
- imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +99 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +99 -11
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
- imap_processing/cli.py +121 -44
- imap_processing/codice/codice_l1a.py +165 -77
- imap_processing/codice/codice_l1b.py +1 -1
- imap_processing/codice/codice_l2.py +118 -19
- imap_processing/codice/constants.py +1217 -1089
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +32 -25
- imap_processing/ena_maps/utils/naming.py +8 -2
- imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
- imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
- imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
- imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
- imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
- imap_processing/glows/l1b/glows_l1b.py +99 -9
- imap_processing/glows/l1b/glows_l1b_data.py +350 -38
- imap_processing/glows/l2/glows_l2.py +11 -0
- imap_processing/hi/hi_l1a.py +124 -3
- imap_processing/hi/hi_l1b.py +154 -71
- imap_processing/hi/hi_l2.py +84 -51
- imap_processing/hi/utils.py +153 -8
- imap_processing/hit/l0/constants.py +3 -0
- imap_processing/hit/l0/decom_hit.py +5 -8
- imap_processing/hit/l1a/hit_l1a.py +375 -45
- imap_processing/hit/l1b/constants.py +5 -0
- imap_processing/hit/l1b/hit_l1b.py +61 -131
- imap_processing/hit/l2/constants.py +1 -1
- imap_processing/hit/l2/hit_l2.py +10 -11
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +32 -1
- imap_processing/ialirt/generate_coverage.py +201 -0
- imap_processing/ialirt/l0/ialirt_spice.py +5 -2
- imap_processing/ialirt/l0/parse_mag.py +337 -29
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/l0/process_swe.py +23 -7
- imap_processing/ialirt/process_ephemeris.py +70 -14
- imap_processing/ialirt/utils/constants.py +22 -16
- imap_processing/ialirt/utils/create_xarray.py +42 -19
- imap_processing/idex/idex_constants.py +1 -5
- imap_processing/idex/idex_l0.py +2 -2
- imap_processing/idex/idex_l1a.py +2 -3
- imap_processing/idex/idex_l1b.py +2 -3
- imap_processing/idex/idex_l2a.py +130 -4
- imap_processing/idex/idex_l2b.py +313 -119
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/l0/lo_apid.py +1 -0
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1a/lo_l1a.py +44 -0
- imap_processing/lo/l1b/lo_l1b.py +3 -3
- imap_processing/lo/l1c/lo_l1c.py +116 -50
- imap_processing/lo/l2/lo_l2.py +29 -29
- imap_processing/lo/lo_ancillary.py +55 -0
- imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
- imap_processing/mag/constants.py +1 -0
- imap_processing/mag/l1a/mag_l1a.py +1 -0
- imap_processing/mag/l1a/mag_l1a_data.py +26 -0
- imap_processing/mag/l1b/mag_l1b.py +3 -2
- imap_processing/mag/l1c/interpolation_methods.py +14 -15
- imap_processing/mag/l1c/mag_l1c.py +23 -6
- imap_processing/mag/l1d/__init__.py +0 -0
- imap_processing/mag/l1d/mag_l1d.py +176 -0
- imap_processing/mag/l1d/mag_l1d_data.py +725 -0
- imap_processing/mag/l2/__init__.py +0 -0
- imap_processing/mag/l2/mag_l2.py +25 -20
- imap_processing/mag/l2/mag_l2_data.py +199 -130
- imap_processing/quality_flags.py +28 -2
- imap_processing/spice/geometry.py +101 -36
- imap_processing/spice/pointing_frame.py +1 -7
- imap_processing/spice/repoint.py +29 -2
- imap_processing/spice/spin.py +32 -8
- imap_processing/spice/time.py +60 -19
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +66 -24
- imap_processing/swapi/swapi_utils.py +1 -1
- imap_processing/swe/l1b/swe_l1b.py +3 -6
- imap_processing/ultra/constants.py +28 -3
- imap_processing/ultra/l0/decom_tools.py +15 -8
- imap_processing/ultra/l0/decom_ultra.py +35 -11
- imap_processing/ultra/l0/ultra_utils.py +102 -12
- imap_processing/ultra/l1a/ultra_l1a.py +26 -6
- imap_processing/ultra/l1b/cullingmask.py +6 -3
- imap_processing/ultra/l1b/de.py +122 -26
- imap_processing/ultra/l1b/extendedspin.py +29 -2
- imap_processing/ultra/l1b/lookup_utils.py +424 -50
- imap_processing/ultra/l1b/quality_flag_filters.py +23 -0
- imap_processing/ultra/l1b/ultra_l1b_culling.py +356 -5
- imap_processing/ultra/l1b/ultra_l1b_extended.py +534 -90
- imap_processing/ultra/l1c/helio_pset.py +127 -7
- imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +90 -15
- imap_processing/ultra/l1c/ultra_l1c.py +6 -0
- imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +446 -341
- imap_processing/ultra/l2/ultra_l2.py +0 -1
- imap_processing/ultra/utils/ultra_l1_utils.py +40 -3
- imap_processing/utils.py +3 -4
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +3 -3
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +126 -126
- imap_processing/idex/idex_l2c.py +0 -250
- imap_processing/spice/kernels.py +0 -187
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
- imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
- imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
- imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Coverage time for each station."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from imap_processing.ialirt.constants import STATIONS
|
|
8
|
+
from imap_processing.ialirt.process_ephemeris import calculate_azimuth_and_elevation
|
|
9
|
+
from imap_processing.spice.time import et_to_utc, str_to_et
|
|
10
|
+
|
|
11
|
+
# Logger setup
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
ALL_STATIONS = [
|
|
15
|
+
*STATIONS.keys(),
|
|
16
|
+
"DSS-24",
|
|
17
|
+
"DSS-25",
|
|
18
|
+
"DSS-26",
|
|
19
|
+
"DSS-34",
|
|
20
|
+
"DSS-35",
|
|
21
|
+
"DSS-36",
|
|
22
|
+
"DSS-53",
|
|
23
|
+
"DSS-54",
|
|
24
|
+
"DSS-55",
|
|
25
|
+
"DSS-56",
|
|
26
|
+
"DSS-74",
|
|
27
|
+
"DSS-75",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_coverage(
|
|
32
|
+
start_time: str,
|
|
33
|
+
outages: dict | None = None,
|
|
34
|
+
dsn: dict | None = None,
|
|
35
|
+
) -> tuple[dict, dict]:
|
|
36
|
+
"""
|
|
37
|
+
Build the output dictionary containing coverage and outage time for each station.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
start_time : str
|
|
42
|
+
Start time in UTC.
|
|
43
|
+
outages : dict, optional
|
|
44
|
+
Dictionary of outages for each station.
|
|
45
|
+
dsn : dict, optional
|
|
46
|
+
Dictionary of Deep Space Network (DSN) stations.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
coverage_dict : dict
|
|
51
|
+
Visibility times per station.
|
|
52
|
+
outage_dict : dict
|
|
53
|
+
Outage times per station.
|
|
54
|
+
"""
|
|
55
|
+
duration_seconds = 24 * 60 * 60 # 86400 seconds in 24 hours
|
|
56
|
+
time_step = 3600 # 1 hr in seconds
|
|
57
|
+
|
|
58
|
+
stations = {
|
|
59
|
+
"Kiel": STATIONS["Kiel"],
|
|
60
|
+
}
|
|
61
|
+
coverage_dict = {}
|
|
62
|
+
outage_dict = {}
|
|
63
|
+
|
|
64
|
+
start_et_input = str_to_et(start_time)
|
|
65
|
+
stop_et_input = start_et_input + duration_seconds
|
|
66
|
+
|
|
67
|
+
time_range = np.arange(start_et_input, stop_et_input, time_step)
|
|
68
|
+
total_visible_mask = np.zeros(time_range.shape, dtype=bool)
|
|
69
|
+
|
|
70
|
+
# Precompute DSN outage mask for non-DSN stations
|
|
71
|
+
dsn_outage_mask = np.zeros(time_range.shape, dtype=bool)
|
|
72
|
+
if dsn:
|
|
73
|
+
for dsn_contacts in dsn.values():
|
|
74
|
+
for start, end in dsn_contacts:
|
|
75
|
+
start_et = str_to_et(start)
|
|
76
|
+
end_et = str_to_et(end)
|
|
77
|
+
dsn_outage_mask |= (time_range >= start_et) & (time_range <= end_et)
|
|
78
|
+
|
|
79
|
+
for station_name, (lon, lat, alt, min_elevation) in stations.items():
|
|
80
|
+
azimuth, elevation = calculate_azimuth_and_elevation(lon, lat, alt, time_range)
|
|
81
|
+
visible = elevation > min_elevation
|
|
82
|
+
|
|
83
|
+
outage_mask = np.zeros(time_range.shape, dtype=bool)
|
|
84
|
+
if outages and station_name in outages:
|
|
85
|
+
for start, end in outages[station_name]:
|
|
86
|
+
start_et = str_to_et(start)
|
|
87
|
+
end_et = str_to_et(end)
|
|
88
|
+
outage_mask |= (time_range >= start_et) & (time_range <= end_et)
|
|
89
|
+
|
|
90
|
+
visible[outage_mask] = False
|
|
91
|
+
# DSN contacts block other stations
|
|
92
|
+
visible[dsn_outage_mask] = False
|
|
93
|
+
total_visible_mask |= visible
|
|
94
|
+
|
|
95
|
+
coverage_dict[station_name] = et_to_utc(time_range[visible], format_str="ISOC")
|
|
96
|
+
outage_dict[station_name] = et_to_utc(
|
|
97
|
+
time_range[outage_mask], format_str="ISOC"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# --- DSN Stations ---
|
|
101
|
+
if dsn:
|
|
102
|
+
for dsn_station, contacts in dsn.items():
|
|
103
|
+
dsn_visible_mask = np.zeros(time_range.shape, dtype=bool)
|
|
104
|
+
for start, end in contacts:
|
|
105
|
+
start_et = str_to_et(start)
|
|
106
|
+
end_et = str_to_et(end)
|
|
107
|
+
dsn_visible_mask |= (time_range >= start_et) & (time_range <= end_et)
|
|
108
|
+
|
|
109
|
+
# Apply DSN outages if present
|
|
110
|
+
outage_mask = np.zeros(time_range.shape, dtype=bool)
|
|
111
|
+
if outages and dsn_station in outages:
|
|
112
|
+
for start, end in outages[dsn_station]:
|
|
113
|
+
start_et = str_to_et(start)
|
|
114
|
+
end_et = str_to_et(end)
|
|
115
|
+
outage_mask |= (time_range >= start_et) & (time_range <= end_et)
|
|
116
|
+
|
|
117
|
+
dsn_visible_mask[outage_mask] = False
|
|
118
|
+
total_visible_mask |= dsn_visible_mask
|
|
119
|
+
|
|
120
|
+
coverage_dict[f"{dsn_station}"] = et_to_utc(
|
|
121
|
+
time_range[dsn_visible_mask], format_str="ISOC"
|
|
122
|
+
)
|
|
123
|
+
outage_dict[f"{dsn_station}"] = et_to_utc(
|
|
124
|
+
time_range[outage_mask], format_str="ISOC"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Total coverage percentage
|
|
128
|
+
total_coverage_percent = (
|
|
129
|
+
np.count_nonzero(total_visible_mask) / time_range.size
|
|
130
|
+
) * 100
|
|
131
|
+
coverage_dict["total_coverage_percent"] = total_coverage_percent
|
|
132
|
+
|
|
133
|
+
# Ensure all stations are present in both dicts
|
|
134
|
+
for station in ALL_STATIONS:
|
|
135
|
+
coverage_dict.setdefault(station, np.array([], dtype="<U23"))
|
|
136
|
+
outage_dict.setdefault(station, np.array([], dtype="<U23"))
|
|
137
|
+
|
|
138
|
+
return coverage_dict, outage_dict
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def format_coverage_summary(
|
|
142
|
+
coverage_dict: dict, outage_dict: dict, start_time: str
|
|
143
|
+
) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
Build the output dictionary containing coverage time for each station.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
coverage_dict : dict
|
|
150
|
+
Coverage for each station, keyed by station name with arrays of UTC times.
|
|
151
|
+
outage_dict : dict
|
|
152
|
+
Outage times for each station, keyed by station name with arrays of UTC times.
|
|
153
|
+
start_time : str
|
|
154
|
+
Start time in UTC.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
output_dict : dict
|
|
159
|
+
Formatted coverage summary.
|
|
160
|
+
"""
|
|
161
|
+
# Include all known stations,
|
|
162
|
+
# plus any new ones that appear in coverage_dict.
|
|
163
|
+
all_stations = ALL_STATIONS + [
|
|
164
|
+
station
|
|
165
|
+
for station in coverage_dict.keys()
|
|
166
|
+
if station not in ALL_STATIONS and station != "total_coverage_percent"
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
duration_seconds = 24 * 60 * 60 # 86400 seconds in 24 hours
|
|
170
|
+
time_step = 3600 # 1 hr in seconds
|
|
171
|
+
|
|
172
|
+
start_et_input = str_to_et(start_time)
|
|
173
|
+
stop_et_input = start_et_input + duration_seconds
|
|
174
|
+
|
|
175
|
+
time_range = np.arange(start_et_input, stop_et_input, time_step)
|
|
176
|
+
all_times = et_to_utc(time_range, format_str="ISOC")
|
|
177
|
+
|
|
178
|
+
data_rows = []
|
|
179
|
+
for time in all_times:
|
|
180
|
+
row = {"time": time}
|
|
181
|
+
for station in all_stations:
|
|
182
|
+
visible_times = coverage_dict.get(station, [])
|
|
183
|
+
outage_times = outage_dict.get(station, [])
|
|
184
|
+
if time in outage_times:
|
|
185
|
+
row[station] = "X"
|
|
186
|
+
elif time in visible_times:
|
|
187
|
+
row[station] = "1"
|
|
188
|
+
else:
|
|
189
|
+
row[station] = "0"
|
|
190
|
+
data_rows.append(row)
|
|
191
|
+
|
|
192
|
+
output_dict = {
|
|
193
|
+
"summary": "I-ALiRT Coverage Summary",
|
|
194
|
+
"generated": start_time,
|
|
195
|
+
"time_format": "UTC (ISOC)",
|
|
196
|
+
"stations": all_stations,
|
|
197
|
+
"total_coverage_percent": round(coverage_dict["total_coverage_percent"], 1),
|
|
198
|
+
"data": data_rows,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return output_dict
|
|
@@ -56,7 +56,10 @@ def get_rotation_matrix(axis: NDArray, angle: NDArray) -> NDArray:
|
|
|
56
56
|
"""
|
|
57
57
|
angle_rad = np.radians(angle)
|
|
58
58
|
rot_matrices = np.array(
|
|
59
|
-
[
|
|
59
|
+
[
|
|
60
|
+
spice.axisar(z, float(phase))
|
|
61
|
+
for z, phase in zip(axis, angle_rad, strict=False)
|
|
62
|
+
]
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
return rot_matrices
|
|
@@ -185,7 +188,7 @@ def transform_instrument_vectors_to_inertial(
|
|
|
185
188
|
vectors = np.array(
|
|
186
189
|
[
|
|
187
190
|
spice.mxv(rot.T.copy(), vec)
|
|
188
|
-
for rot, vec in zip(total_rotations, instrument_vectors)
|
|
191
|
+
for rot, vec in zip(total_rotations, instrument_vectors, strict=False)
|
|
189
192
|
]
|
|
190
193
|
)
|
|
191
194
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Union
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
7
|
import xarray as xr
|
|
9
8
|
|
|
9
|
+
from imap_processing.ialirt.l0.ialirt_spice import (
|
|
10
|
+
transform_instrument_vectors_to_inertial,
|
|
11
|
+
)
|
|
10
12
|
from imap_processing.ialirt.l0.mag_l0_ialirt_data import (
|
|
11
13
|
Packet0,
|
|
12
14
|
Packet1,
|
|
@@ -20,7 +22,15 @@ from imap_processing.mag.l1b.mag_l1b import (
|
|
|
20
22
|
calibrate_vector,
|
|
21
23
|
shift_time,
|
|
22
24
|
)
|
|
23
|
-
from imap_processing.
|
|
25
|
+
from imap_processing.mag.l1d.mag_l1d_data import MagL1d
|
|
26
|
+
from imap_processing.mag.l2.mag_l2_data import MagL2L1dBase
|
|
27
|
+
from imap_processing.spice.geometry import (
|
|
28
|
+
SpiceFrame,
|
|
29
|
+
cartesian_to_spherical,
|
|
30
|
+
frame_transform,
|
|
31
|
+
spherical_to_cartesian,
|
|
32
|
+
)
|
|
33
|
+
from imap_processing.spice.time import met_to_ttj2000ns, met_to_utc, ttj2000ns_to_et
|
|
24
34
|
|
|
25
35
|
logger = logging.getLogger(__name__)
|
|
26
36
|
|
|
@@ -193,7 +203,7 @@ def get_time(
|
|
|
193
203
|
(grouped_data["group"] == group).values
|
|
194
204
|
][pkt_counter == 2]
|
|
195
205
|
|
|
196
|
-
time_data: dict[str,
|
|
206
|
+
time_data: dict[str, int | float] = {
|
|
197
207
|
"pri_coarsetm": int(pri_coarsetm.item()),
|
|
198
208
|
"pri_fintm": int(pri_fintm.item()),
|
|
199
209
|
"sec_coarsetm": int(sec_coarsetm.item()),
|
|
@@ -286,9 +296,238 @@ def calculate_l1b(
|
|
|
286
296
|
return updated_vector_mago, updated_vector_magi, time_data
|
|
287
297
|
|
|
288
298
|
|
|
299
|
+
def calibrate_and_offset_vectors(
|
|
300
|
+
vectors: np.ndarray,
|
|
301
|
+
calibration: np.ndarray,
|
|
302
|
+
offsets: np.ndarray,
|
|
303
|
+
is_magi: bool = False,
|
|
304
|
+
) -> np.ndarray:
|
|
305
|
+
"""
|
|
306
|
+
Apply calibration and offsets to magnetic vectors.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
vectors : np.ndarray
|
|
311
|
+
Raw magnetic vectors, shape (n, 4).
|
|
312
|
+
calibration : np.ndarray
|
|
313
|
+
Calibration matrix, shape (3, 3, 4).
|
|
314
|
+
offsets : np.ndarray
|
|
315
|
+
Offsets array, shape (2, 4, 3) where:
|
|
316
|
+
- index 0 = MAGo, 1 = MAGi
|
|
317
|
+
- second index = range (0–3)
|
|
318
|
+
- third index = axis (x, y, z)
|
|
319
|
+
is_magi : bool, optional
|
|
320
|
+
True if applying to MAGi data, False for MAGo.
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
calibrated_and_offset_vectors : np.ndarray
|
|
325
|
+
Calibrated and offset vectors, shape (n, 3).
|
|
326
|
+
"""
|
|
327
|
+
# Apply calibration matrix -> (n,4)
|
|
328
|
+
# apply_calibration_offset_single_vector
|
|
329
|
+
calibrated = MagL2L1dBase.apply_calibration(vectors.reshape(1, 4), calibration)
|
|
330
|
+
|
|
331
|
+
# Apply offsets per vector
|
|
332
|
+
# vec shape (4)
|
|
333
|
+
# offsets shape (2, 4, 3) where first index is 0 for MAGo and 1 for MAGi
|
|
334
|
+
calibrated = np.array(
|
|
335
|
+
[
|
|
336
|
+
MagL1d.apply_calibration_offset_single_vector(vec, offsets, is_magi=is_magi)
|
|
337
|
+
for vec in calibrated
|
|
338
|
+
]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return calibrated[:, :3]
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def apply_gradiometry_correction(
|
|
345
|
+
mago_vectors_eclipj2000: np.ndarray,
|
|
346
|
+
mago_time_data: np.ndarray,
|
|
347
|
+
magi_vectors_eclipj2000: np.ndarray,
|
|
348
|
+
magi_time_data: np.ndarray,
|
|
349
|
+
gradiometer_factor: np.ndarray,
|
|
350
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
351
|
+
"""
|
|
352
|
+
Align MAGi to MAGo timestamps and apply gradiometry correction.
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
mago_vectors_eclipj2000 : np.ndarray
|
|
357
|
+
MAGo vectors in inertial frame, shape (N, 3).
|
|
358
|
+
mago_time_data : np.ndarray
|
|
359
|
+
Time for primary sensor, shape (N, 3).
|
|
360
|
+
magi_vectors_eclipj2000 : np.ndarray
|
|
361
|
+
MAGi vectors in inertial frame, shape (M, 3).
|
|
362
|
+
magi_time_data : np.ndarray
|
|
363
|
+
Time for secondary sensor, shape (N, 3).
|
|
364
|
+
gradiometer_factor : np.ndarray
|
|
365
|
+
A (3,3) element matrix to scale and rotate the gradiometer offsets.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
mago_corrected : np.ndarray
|
|
370
|
+
Corrected MAGo vectors in inertial frame, shape (N, 3).
|
|
371
|
+
magnitude : np.ndarray
|
|
372
|
+
Magnitude of corrected MAGo vectors, shape (N,).
|
|
373
|
+
"""
|
|
374
|
+
gradiometry_offsets = MagL1d.calculate_gradiometry_offsets(
|
|
375
|
+
mago_vectors_eclipj2000,
|
|
376
|
+
mago_time_data,
|
|
377
|
+
magi_vectors_eclipj2000,
|
|
378
|
+
magi_time_data,
|
|
379
|
+
)
|
|
380
|
+
mago_corrected = MagL1d.apply_gradiometry_offsets(
|
|
381
|
+
gradiometry_offsets, mago_vectors_eclipj2000, gradiometer_factor
|
|
382
|
+
)
|
|
383
|
+
magnitude = np.linalg.norm(mago_corrected, axis=-1).squeeze()
|
|
384
|
+
|
|
385
|
+
return mago_corrected, magnitude
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def transform_to_inertial(
|
|
389
|
+
sc_spin_phase_rad: np.ndarray,
|
|
390
|
+
sc_inertial_right: np.ndarray,
|
|
391
|
+
sc_inertial_decline: np.ndarray,
|
|
392
|
+
attitude_time: np.ndarray,
|
|
393
|
+
target_time: float,
|
|
394
|
+
mag_vector: np.ndarray,
|
|
395
|
+
) -> np.ndarray:
|
|
396
|
+
"""
|
|
397
|
+
Transform vector to ECLIPJ2000.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
sc_spin_phase_rad : numpy.ndarray
|
|
402
|
+
Spin phase for 4 packets 0 to 2π radians, shape (4).
|
|
403
|
+
sc_inertial_right : numpy.ndarray
|
|
404
|
+
Inertial right ascension for 4 packets 0 to 2π radians, shape (4).
|
|
405
|
+
sc_inertial_decline : numpy.ndarray
|
|
406
|
+
Inertial declination for 4 packets -π/2 to π/2 radians, shape (4).
|
|
407
|
+
attitude_time : np.ndarray
|
|
408
|
+
Timestamps for the 4 packets.
|
|
409
|
+
Example: test_met = grouped_data["met"][
|
|
410
|
+
(grouped_data["group"] == group).values].
|
|
411
|
+
ttj2000ns = met_to_ttj2000ns(test_met.values).
|
|
412
|
+
target_time : float
|
|
413
|
+
Time at which to apply the transformation.
|
|
414
|
+
Will be primary_epoch (mago vector) or secondary_epoch (magi vector).
|
|
415
|
+
Example: time_data['primary_epoch'].
|
|
416
|
+
mag_vector : numpy.ndarray
|
|
417
|
+
Vector, shape (3).
|
|
418
|
+
|
|
419
|
+
Returns
|
|
420
|
+
-------
|
|
421
|
+
inertial_vector : np.ndarray
|
|
422
|
+
Transformed vector in the ECLIPJ2000 frame, shape (3,).
|
|
423
|
+
|
|
424
|
+
Notes
|
|
425
|
+
-----
|
|
426
|
+
The MAG vectors are calculated based on 4 packets,
|
|
427
|
+
each of which contains its own spin phase,
|
|
428
|
+
inertial right ascension, and inertial decline.
|
|
429
|
+
"""
|
|
430
|
+
if target_time < attitude_time.min() or target_time > attitude_time.max():
|
|
431
|
+
logger.warning(
|
|
432
|
+
f"target_time {target_time} is outside attitude_time bounds "
|
|
433
|
+
f"[{attitude_time.min()}, {attitude_time.max()}]; using edge values."
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Get sort order based on attitude_time
|
|
437
|
+
sort_idx = np.argsort(attitude_time)
|
|
438
|
+
|
|
439
|
+
# Sort all arrays accordingly
|
|
440
|
+
attitude_time = attitude_time[sort_idx]
|
|
441
|
+
sc_spin_phase_rad = sc_spin_phase_rad[sort_idx]
|
|
442
|
+
sc_inertial_right = sc_inertial_right[sort_idx]
|
|
443
|
+
sc_inertial_decline = sc_inertial_decline[sort_idx]
|
|
444
|
+
|
|
445
|
+
# Interpolate spin phase, RA, and Dec at target_time
|
|
446
|
+
# Convert RA/Dec to unit cartesian vectors
|
|
447
|
+
spherical_coords = np.stack(
|
|
448
|
+
[
|
|
449
|
+
np.ones_like(sc_inertial_right),
|
|
450
|
+
np.degrees(sc_inertial_right),
|
|
451
|
+
np.degrees(sc_inertial_decline),
|
|
452
|
+
],
|
|
453
|
+
axis=-1,
|
|
454
|
+
)
|
|
455
|
+
vecs = spherical_to_cartesian(spherical_coords)
|
|
456
|
+
|
|
457
|
+
# Interpolate in Cartesian space
|
|
458
|
+
vx = np.interp(target_time, attitude_time, vecs[:, 0])
|
|
459
|
+
vy = np.interp(target_time, attitude_time, vecs[:, 1])
|
|
460
|
+
vz = np.interp(target_time, attitude_time, vecs[:, 2])
|
|
461
|
+
v_interp = np.array([vx, vy, vz])
|
|
462
|
+
# Normalize vector so that its magnitude is 1.
|
|
463
|
+
v_interp /= np.linalg.norm(v_interp)
|
|
464
|
+
|
|
465
|
+
# Convert back to spherical
|
|
466
|
+
ra_dec = cartesian_to_spherical(v_interp)
|
|
467
|
+
ra_deg = ra_dec[1]
|
|
468
|
+
dec_deg = ra_dec[2]
|
|
469
|
+
|
|
470
|
+
# Account for discontinuities in spin phase.
|
|
471
|
+
spin_phase_unwrapped = np.unwrap(sc_spin_phase_rad)
|
|
472
|
+
spin_phase_interp = np.interp(target_time, attitude_time, spin_phase_unwrapped)
|
|
473
|
+
spin_phase_deg = np.degrees(spin_phase_interp) % 360
|
|
474
|
+
|
|
475
|
+
# Transform each into ECLIPJ2000
|
|
476
|
+
inertial_vector = transform_instrument_vectors_to_inertial(
|
|
477
|
+
np.asarray(mag_vector).reshape(1, 3),
|
|
478
|
+
np.array([spin_phase_deg]),
|
|
479
|
+
np.array([ra_deg]),
|
|
480
|
+
np.array([dec_deg]),
|
|
481
|
+
)[0]
|
|
482
|
+
|
|
483
|
+
return inertial_vector
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def transform_to_frames(
|
|
487
|
+
target_time: np.ndarray,
|
|
488
|
+
inertial_vector: np.ndarray,
|
|
489
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
490
|
+
"""
|
|
491
|
+
Transform vector to different frames.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
target_time : np.ndarray
|
|
496
|
+
Time at which to apply the transformation.
|
|
497
|
+
Will be primary_epoch (mago vector).
|
|
498
|
+
Example: time_data['primary_epoch'].
|
|
499
|
+
inertial_vector : np.ndarray
|
|
500
|
+
Transformed vector in the ECLIPJ2000 frame, shape (3,).
|
|
501
|
+
|
|
502
|
+
Returns
|
|
503
|
+
-------
|
|
504
|
+
gse_vector : np.ndarray
|
|
505
|
+
Transformed vector in the GSE frame, shape (3,).
|
|
506
|
+
gsm_vector : np.ndarray
|
|
507
|
+
Transformed vector in the GSM frame, shape (3,).
|
|
508
|
+
rtn_vector : np.ndarray
|
|
509
|
+
Transformed vector in the RTN frame, shape (3,).
|
|
510
|
+
"""
|
|
511
|
+
et_target_time = ttj2000ns_to_et(target_time)
|
|
512
|
+
|
|
513
|
+
gse_vector = frame_transform(
|
|
514
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_GSE
|
|
515
|
+
)
|
|
516
|
+
gsm_vector = frame_transform(
|
|
517
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_GSM
|
|
518
|
+
)
|
|
519
|
+
rtn_vector = frame_transform(
|
|
520
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_RTN
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
return gse_vector, gsm_vector, rtn_vector
|
|
524
|
+
|
|
525
|
+
|
|
289
526
|
def process_packet(
|
|
290
|
-
accumulated_data: xr.Dataset,
|
|
291
|
-
|
|
527
|
+
accumulated_data: xr.Dataset,
|
|
528
|
+
engineering_calibration_dataset: xr.Dataset,
|
|
529
|
+
l1d_calibration_dataset: xr.Dataset,
|
|
530
|
+
) -> list[dict]:
|
|
292
531
|
"""
|
|
293
532
|
Parse the MAG packets.
|
|
294
533
|
|
|
@@ -296,8 +535,10 @@ def process_packet(
|
|
|
296
535
|
----------
|
|
297
536
|
accumulated_data : xr.Dataset
|
|
298
537
|
Packets dataset accumulated over 1 min.
|
|
299
|
-
|
|
300
|
-
|
|
538
|
+
engineering_calibration_dataset : xr.Dataset
|
|
539
|
+
Engineering calibration dataset.
|
|
540
|
+
l1d_calibration_dataset : xr.Dataset
|
|
541
|
+
L1D calibration dataset.
|
|
301
542
|
|
|
302
543
|
Returns
|
|
303
544
|
-------
|
|
@@ -323,8 +564,12 @@ def process_packet(
|
|
|
323
564
|
grouped_data = find_groups(accumulated_data, (0, 3), "pkt_counter", "met")
|
|
324
565
|
|
|
325
566
|
unique_groups = np.unique(grouped_data["group"])
|
|
326
|
-
l1b_data = []
|
|
327
567
|
mag_data = []
|
|
568
|
+
met_all = []
|
|
569
|
+
mago_vectors_all = []
|
|
570
|
+
mago_times_all = []
|
|
571
|
+
magi_vectors_all = []
|
|
572
|
+
magi_times_all = []
|
|
328
573
|
|
|
329
574
|
for group in unique_groups:
|
|
330
575
|
# Get status values for each group.
|
|
@@ -360,7 +605,7 @@ def process_packet(
|
|
|
360
605
|
pkt_counter,
|
|
361
606
|
science_data,
|
|
362
607
|
status_data,
|
|
363
|
-
|
|
608
|
+
engineering_calibration_dataset,
|
|
364
609
|
)
|
|
365
610
|
|
|
366
611
|
# Note: primary = MAGo, secondary = MAGi.
|
|
@@ -371,36 +616,99 @@ def process_packet(
|
|
|
371
616
|
if status_data["sec_isvalid"] == 0:
|
|
372
617
|
updated_vector_magi = np.full(4, -32768)
|
|
373
618
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
619
|
+
mago_calibration = l1d_calibration_dataset["URFTOORFO"][0]
|
|
620
|
+
magi_calibration = l1d_calibration_dataset["URFTOORFI"][0]
|
|
621
|
+
offsets = l1d_calibration_dataset["offsets"][0]
|
|
622
|
+
|
|
623
|
+
mago_out = calibrate_and_offset_vectors(
|
|
624
|
+
updated_vector_mago, mago_calibration, offsets, is_magi=False
|
|
625
|
+
)
|
|
626
|
+
magi_out = calibrate_and_offset_vectors(
|
|
627
|
+
updated_vector_magi, magi_calibration, offsets, is_magi=True
|
|
383
628
|
)
|
|
629
|
+
sc_spin_phase_rad = grouped_data["sc_spin_phase"][
|
|
630
|
+
(grouped_data["group"] == group).values
|
|
631
|
+
]
|
|
632
|
+
sc_inertial_right = grouped_data["sc_inertial_right"][
|
|
633
|
+
(grouped_data["group"] == group).values
|
|
634
|
+
]
|
|
635
|
+
sc_inertial_decline = grouped_data["sc_inertial_decline"][
|
|
636
|
+
(grouped_data["group"] == group).values
|
|
637
|
+
]
|
|
384
638
|
|
|
385
|
-
|
|
639
|
+
attitude_time = met_to_ttj2000ns(
|
|
640
|
+
grouped_data["met"][(grouped_data["group"] == group).values]
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# Convert to ECLIPJ2000 frame.
|
|
644
|
+
mago_inertial_vector = transform_to_inertial(
|
|
645
|
+
sc_spin_phase_rad.values,
|
|
646
|
+
sc_inertial_right.values,
|
|
647
|
+
sc_inertial_decline.values,
|
|
648
|
+
attitude_time,
|
|
649
|
+
time_data["primary_epoch"],
|
|
650
|
+
mago_out,
|
|
651
|
+
)
|
|
652
|
+
magi_inertial_vector = transform_to_inertial(
|
|
653
|
+
sc_spin_phase_rad.values,
|
|
654
|
+
sc_inertial_right.values,
|
|
655
|
+
sc_inertial_decline.values,
|
|
656
|
+
attitude_time,
|
|
657
|
+
time_data["secondary_epoch"],
|
|
658
|
+
magi_out,
|
|
659
|
+
)
|
|
386
660
|
|
|
387
|
-
# Placeholder for real data.
|
|
388
661
|
met = grouped_data["met"][(grouped_data["group"] == group).values]
|
|
662
|
+
met_all.append(met.values[0])
|
|
663
|
+
mago_times_all.append(time_data["primary_epoch"])
|
|
664
|
+
mago_vectors_all.append(mago_inertial_vector)
|
|
665
|
+
magi_vectors_all.append(magi_inertial_vector)
|
|
666
|
+
magi_times_all.append(time_data["secondary_epoch"])
|
|
667
|
+
|
|
668
|
+
mago_corrected, magnitude = apply_gradiometry_correction(
|
|
669
|
+
np.array(mago_vectors_all),
|
|
670
|
+
np.array(mago_times_all),
|
|
671
|
+
np.array(magi_vectors_all),
|
|
672
|
+
np.array(magi_times_all),
|
|
673
|
+
l1d_calibration_dataset["gradiometer_factor"].values.squeeze(),
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
gse_vector, gsm_vector, rtn_vector = transform_to_frames(
|
|
677
|
+
np.array(mago_times_all), mago_corrected
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
spherical = cartesian_to_spherical(gsm_vector)
|
|
681
|
+
phi_gsm = spherical[:, 1]
|
|
682
|
+
theta_gsm = spherical[:, 2]
|
|
683
|
+
|
|
684
|
+
spherical = cartesian_to_spherical(gse_vector)
|
|
685
|
+
phi_gse = spherical[:, 1]
|
|
686
|
+
theta_gse = spherical[:, 2]
|
|
687
|
+
|
|
688
|
+
# Omit the first value since we expect it to be extrapolated.
|
|
689
|
+
for i in range(len(mago_corrected)):
|
|
690
|
+
if i == 0:
|
|
691
|
+
continue
|
|
692
|
+
|
|
389
693
|
mag_data.append(
|
|
390
694
|
{
|
|
391
695
|
"apid": 478,
|
|
392
|
-
"met": int(
|
|
393
|
-
"met_in_utc": met_to_utc(
|
|
394
|
-
"ttj2000ns": int(met_to_ttj2000ns(
|
|
395
|
-
"
|
|
396
|
-
"
|
|
397
|
-
"
|
|
398
|
-
"
|
|
399
|
-
"
|
|
696
|
+
"met": int(met_all[i]),
|
|
697
|
+
"met_in_utc": met_to_utc(met_all[i]).split(".")[0],
|
|
698
|
+
"ttj2000ns": int(met_to_ttj2000ns(met_all[i])),
|
|
699
|
+
"mag_epoch": int(mago_times_all[i]),
|
|
700
|
+
"mag_B_GSE": [Decimal(str(v)) for v in gse_vector[i]],
|
|
701
|
+
"mag_B_GSM": [Decimal(str(v)) for v in gsm_vector[i]],
|
|
702
|
+
"mag_B_RTN": [Decimal(str(v)) for v in rtn_vector[i]],
|
|
703
|
+
"mag_B_magnitude": Decimal(str(magnitude[i])),
|
|
704
|
+
"mag_phi_B_GSM": Decimal(str(phi_gsm[i])),
|
|
705
|
+
"mag_theta_B_GSM": Decimal(str(theta_gsm[i])),
|
|
706
|
+
"mag_phi_B_GSE": Decimal(str(phi_gse[i])),
|
|
707
|
+
"mag_theta_B_GSE": Decimal(str(theta_gse[i])),
|
|
400
708
|
}
|
|
401
709
|
)
|
|
402
710
|
|
|
403
|
-
return mag_data
|
|
711
|
+
return mag_data
|
|
404
712
|
|
|
405
713
|
|
|
406
714
|
def retrieve_matrix_from_single_l1b_calibration(
|
|
@@ -91,18 +91,20 @@ def create_l1(
|
|
|
91
91
|
fast_rate_1_dict = {
|
|
92
92
|
prefix: value
|
|
93
93
|
for prefix, value in zip(
|
|
94
|
-
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_1"], fast_rate_1.data
|
|
94
|
+
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_1"], fast_rate_1.data, strict=False
|
|
95
95
|
)
|
|
96
96
|
}
|
|
97
97
|
fast_rate_2_dict = {
|
|
98
98
|
prefix: value
|
|
99
99
|
for prefix, value in zip(
|
|
100
|
-
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_2"], fast_rate_2.data
|
|
100
|
+
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_2"], fast_rate_2.data, strict=False
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
slow_rate_dict = {
|
|
104
104
|
prefix: value
|
|
105
|
-
for prefix, value in zip(
|
|
105
|
+
for prefix, value in zip(
|
|
106
|
+
HIT_PREFIX_TO_RATE_TYPE["SLOW_RATE"], slow_rate.data, strict=False
|
|
107
|
+
)
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
l1 = {**fast_rate_1_dict, **fast_rate_2_dict, **slow_rate_dict}
|