Skip to content

Commit a6e8f1a

Browse files
authored
ENH/FIX: Add segmented packet handling for Ultra packets (#2581)
This adds a helper function to combine segmented packets together after XTCE parsing. We can't handle this in the XTCE definition itself due to the variable length packets. We also need to update the Ultra packet definition to have a larger variable length field because the header items are actually only stored in the first packet, not all the middle/last packets too.
1 parent 69a05dd commit a6e8f1a

File tree

4 files changed

+194
-433
lines changed

4 files changed

+194
-433
lines changed

imap_processing/tests/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,50 @@ def test_packet_file_to_datasets_flat_definition():
262262
utils.packet_file_to_datasets(packet_files, packet_definition)
263263

264264

265+
def test_combine_segmented_packets():
266+
"""Test combine_segmented_packets function."""
267+
268+
# unsegmented, first, middle, last, unsegmented
269+
sequence_flags = xr.DataArray(np.array([3, 1, 0, 2, 3]), dims=["epoch"])
270+
271+
binary_data = xr.DataArray(
272+
np.array(
273+
[
274+
b"ABC",
275+
b"123",
276+
b"456",
277+
b"789",
278+
b"abc",
279+
],
280+
dtype=object,
281+
),
282+
dims=["epoch"],
283+
)
284+
285+
ds = xr.Dataset(data_vars={"seq_flgs": sequence_flags, "packetdata": binary_data})
286+
287+
combined_ds = utils.combine_segmented_packets(ds, "packetdata")
288+
289+
expected_ds = xr.Dataset(
290+
data_vars={
291+
"seq_flgs": xr.DataArray(np.array([3, 1, 3]), dims=["epoch"]),
292+
"packetdata": xr.DataArray(
293+
np.array(
294+
[
295+
b"ABC",
296+
b"123456789",
297+
b"abc",
298+
],
299+
dtype=object,
300+
),
301+
dims=["epoch"],
302+
),
303+
}
304+
)
305+
306+
xr.testing.assert_equal(combined_ds, expected_ds)
307+
308+
265309
def test_extract_data_dict():
266310
"""Test extract_data_dict function."""
267311
data_vars = {

imap_processing/ultra/l0/decom_ultra.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,73 @@
3232
ULTRA_RATES,
3333
PacketProperties,
3434
)
35-
from imap_processing.utils import convert_to_binary_string
35+
from imap_processing.utils import combine_segmented_packets, convert_to_binary_string
3636

3737
logger = logging.getLogger(__name__)
3838

3939

40+
def extract_initial_items_from_combined_packets(
41+
packets: xr.Dataset,
42+
) -> xr.Dataset:
43+
"""
44+
Extract metadata fields from the beginning of combined event_data packets.
45+
46+
Extracts bit fields from the first 20 bytes of each event_data array
47+
and adds them as new variables to the dataset.
48+
49+
Parameters
50+
----------
51+
packets : xarray.Dataset
52+
Dataset containing combined packets with event_data.
53+
54+
Returns
55+
-------
56+
xarray.Dataset
57+
Dataset with extracted metadata fields added.
58+
"""
59+
# Initialize arrays for extracted fields
60+
n_packets = len(packets.epoch)
61+
62+
# Preallocate arrays
63+
sid = np.zeros(n_packets, dtype=np.uint8)
64+
spin = np.zeros(n_packets, dtype=np.uint8)
65+
abortflag = np.zeros(n_packets, dtype=np.uint8)
66+
startdelay = np.zeros(n_packets, dtype=np.uint16)
67+
p00 = np.zeros(n_packets, dtype=np.uint8)
68+
69+
# Extract the data array outside of the loop
70+
binary_data = packets["packetdata"].data
71+
# Extract fields from each packet
72+
for pkt_idx in range(n_packets):
73+
event_data = binary_data[pkt_idx]
74+
75+
sid[pkt_idx] = event_data[0]
76+
spin[pkt_idx] = event_data[1]
77+
abortflag[pkt_idx] = (event_data[2] >> 7) & 0x1
78+
startdelay[pkt_idx] = int.from_bytes(event_data[2:4], byteorder="big") & 0x7FFF
79+
p00[pkt_idx] = event_data[4]
80+
81+
# Remove the first 5 bytes after extraction
82+
binary_data[pkt_idx] = event_data[5:]
83+
84+
# Add extracted fields to dataset
85+
packets["sid"] = xr.DataArray(sid, dims=["epoch"])
86+
packets["spin"] = xr.DataArray(spin, dims=["epoch"])
87+
packets["abortflag"] = xr.DataArray(abortflag, dims=["epoch"])
88+
packets["startdelay"] = xr.DataArray(startdelay, dims=["epoch"])
89+
packets["p00"] = xr.DataArray(p00, dims=["epoch"])
90+
91+
return packets
92+
93+
4094
def process_ultra_tof(ds: xr.Dataset, packet_props: PacketProperties) -> xr.Dataset:
4195
"""
4296
Unpack and decode Ultra TOF packets.
4397
98+
The TOF packets contain image data that may be split across multiple segmented
99+
packets. This function combines the segmented packets and decompresses the image
100+
data.
101+
44102
Parameters
45103
----------
46104
ds : xarray.Dataset
@@ -54,6 +112,11 @@ def process_ultra_tof(ds: xr.Dataset, packet_props: PacketProperties) -> xr.Data
54112
dataset : xarray.Dataset
55113
Dataset containing the decoded and decompressed data.
56114
"""
115+
# Combine segmented packets
116+
ds = combine_segmented_packets(ds, binary_field_name="packetdata")
117+
# Extract the header keys from each of the combined packetdata fields.
118+
ds = extract_initial_items_from_combined_packets(ds)
119+
57120
scalar_keys = [key for key in ds.data_vars if key not in ("packetdata", "sid")]
58121

59122
image_planes = packet_props.image_planes

0 commit comments

Comments
 (0)