Skip to content

Commit d0a1873

Browse files
committedMar 8, 2025··
Add spp.create_packet_list function
1 parent bc05878 commit d0a1873

File tree

5 files changed

+96
-40
lines changed

5 files changed

+96
-40
lines changed
 

‎docs/source/changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Release notes for the `space_packet_parser` library
1111
- *BREAKING*: Reorganization of the project into different submodules for more explicit handling
1212
of imports. There is now an `space_packet_parser.xtce` module with xtce representations separated
1313
into modules underneath that.
14+
- Add `spp.create_packet_list` function to directly create a list from packet files and a definition.
1415
- Add support for creating a packet definition from Python objects and serializing it as XML.
1516
- BUGFIX: Fix kbps calculation in packet generator for showing progress.
1617
- Add support for string and float encoded enumerated lookup parameters.

‎space_packet_parser/__init__.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Space Packet Parser"""
2+
from collections.abc import Iterable
23
from pathlib import Path
34
from typing import Union
45

@@ -15,6 +16,43 @@ def load_xtce(filename: Union[str, Path]) -> XtcePacketDefinition:
1516
1617
Returns
1718
-------
18-
: definitions.XtcePacketDefinition
19+
: XtcePacketDefinition
1920
"""
2021
return XtcePacketDefinition.from_xtce(filename)
22+
23+
24+
def create_packet_list(
25+
packet_files: Union[str, Path, Iterable[Union[str, Path]]],
26+
xtce_packet_definition: Union[str, Path, XtcePacketDefinition],
27+
**packet_generator_kwargs: any
28+
):
29+
"""Directly create a list of Packet objects directly from one or more binary files and an XTCE definition
30+
31+
Parameters
32+
----------
33+
packet_files : Union[str, Path, Iterable[Union[str, Path]]]
34+
Packet files
35+
xtce_packet_definition : Union[str, Path, xtce.definitions.XtcePacketDefinition]
36+
Packet definition for parsing the packet data
37+
packet_generator_kwargs : Optional[dict]
38+
Keyword arguments passed to `XtcePacketDefinition.packet_generator()`
39+
40+
Returns
41+
-------
42+
: list[packets.Packet]
43+
List of parsed Packet objects. Can be used like a list of dictionaries.
44+
"""
45+
packet_generator_kwargs = packet_generator_kwargs or {}
46+
47+
if not isinstance(xtce_packet_definition, XtcePacketDefinition):
48+
xtce_packet_definition = XtcePacketDefinition.from_xtce(xtce_packet_definition)
49+
50+
if isinstance(packet_files, (str, Path)):
51+
packet_files = [packet_files]
52+
53+
packet_list = []
54+
for packet_file in packet_files:
55+
with open(packet_file, "rb") as f:
56+
packet_list += list(xtce_packet_definition.packet_generator(f, **packet_generator_kwargs))
57+
58+
return packet_list

‎space_packet_parser/xarr.py

+33-33
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ def create_dataset(
155155
packet_files = [packet_files]
156156

157157
# Set up containers to store our data
158-
# We are getting a packet file that may contain multiple apids
159-
# Each apid is expected to contain consistent data fields, so we want to create a
160-
# dataset per apid.
158+
# We are getting a packet file that may contain multiple APIDs
159+
# Each APID is expected to contain consistent data fields, so we want to create a
160+
# dataset per APID.
161161
# {apid1: dataset1, apid2: dataset2, ...}
162162
data_dict: dict[int, dict] = {}
163163
# Also keep track of the datatype mapping for each field
@@ -167,38 +167,38 @@ def create_dataset(
167167

168168
for packet_file in packet_files:
169169
with open(packet_file, "rb") as f:
170-
packet_generator = list(xtce_packet_definition.packet_generator(f, **packet_generator_kwargs))
171-
172-
for packet in packet_generator:
173-
apid = packet.raw_data.apid
174-
if apid not in data_dict:
175-
# This is the first packet for this APID
176-
data_dict[apid] = collections.defaultdict(list)
177-
datatype_mapping[apid] = {}
178-
variable_mapping[apid] = packet.keys()
179-
180-
if variable_mapping[apid] != packet.keys():
181-
raise ValueError(
182-
f"Packet fields do not match for APID {apid}. This could be "
183-
f"due to a conditional (polymorphic) packet definition in the XTCE, while this "
184-
f"function currently only supports flat packet definitions."
185-
f"\nExpected: {variable_mapping[apid]},\ngot: {list(packet.keys())}"
186-
)
187-
188-
for key, value in packet.items():
189-
if use_raw_values:
190-
# Use the derived value if it exists, otherwise use the raw value
191-
val = value.raw_value
192-
else:
193-
val = value
194-
195-
data_dict[apid][key].append(val)
196-
if key not in datatype_mapping[apid]:
197-
# Add this datatype to the mapping
198-
datatype_mapping[apid][key] = _get_minimum_numpy_datatype(
199-
key, xtce_packet_definition, use_raw_value=use_raw_values
170+
packet_generator = xtce_packet_definition.packet_generator(f, **packet_generator_kwargs)
171+
172+
for packet in packet_generator:
173+
apid = packet.raw_data.apid
174+
if apid not in data_dict:
175+
# This is the first packet for this APID
176+
data_dict[apid] = collections.defaultdict(list)
177+
datatype_mapping[apid] = {}
178+
variable_mapping[apid] = set(packet.keys())
179+
180+
if variable_mapping[apid] != packet.keys():
181+
raise ValueError(
182+
f"Packet fields do not match for APID {apid}. This could be "
183+
f"due to a conditional (polymorphic) packet definition in the XTCE, while this "
184+
f"function currently only supports flat packet definitions."
185+
f"\nExpected: {variable_mapping[apid]},\ngot: {list(packet.keys())}"
200186
)
201187

188+
for key, value in packet.items():
189+
if use_raw_values:
190+
# Use the derived value if it exists, otherwise use the raw value
191+
val = value.raw_value
192+
else:
193+
val = value
194+
195+
data_dict[apid][key].append(val)
196+
if key not in datatype_mapping[apid]:
197+
# Add this datatype to the mapping
198+
datatype_mapping[apid][key] = _get_minimum_numpy_datatype(
199+
key, xtce_packet_definition, use_raw_value=use_raw_values
200+
)
201+
202202
# Turn the dict into an xarray dataset
203203
dataset_by_apid = {}
204204

‎tests/integration/test_xarr.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ def test_create_xarray_dataset_ctim(ctim_test_data_dir, caplog):
4444
"""CTIM data contains many APIDs"""
4545
packet_file = ctim_test_data_dir / "ccsds_2021_155_14_39_51"
4646
definition_file = ctim_test_data_dir / "ctim_xtce_v1.xml"
47-
ds = create_dataset(packet_file, definition_file, root_container_name="CCSDSTelemetryPacket", parse_bad_pkts=False)
48-
print(ds)
47+
_ = create_dataset(packet_file, definition_file, root_container_name="CCSDSTelemetryPacket", parse_bad_pkts=False)
4948

5049

5150
def test_create_xarray_dataset_suda(suda_test_data_dir):
@@ -54,4 +53,4 @@ def test_create_xarray_dataset_suda(suda_test_data_dir):
5453
definition_file = suda_test_data_dir / "suda_combined_science_definition.xml"
5554
# SUDA has a polymorphic packet structure
5655
with pytest.raises(ValueError, match="Packet fields do not match for APID 1425"):
57-
create_dataset(packet_file, definition_file, skip_header_bytes=4)
56+
_ = create_dataset(packet_file, definition_file, skip_header_bytes=4)
+21-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
"""Tests for main space_packet_parser.__init__ module"""
2-
import space_packet_parser
2+
import space_packet_parser as spp
33
from space_packet_parser.xtce import definitions
44

55

66
def test_load_xtce(jpss_test_data_dir, tmp_path):
77
"""Test high level function for loading an XTCE definition file"""
8-
xtcedef = space_packet_parser.load_xtce(jpss_test_data_dir / "jpss1_geolocation_xtce_v1.xml")
8+
xtcedef = spp.load_xtce(jpss_test_data_dir / "jpss1_geolocation_xtce_v1.xml")
99
assert isinstance(xtcedef, definitions.XtcePacketDefinition)
1010

1111
outpath = tmp_path / "test_output.xml"
1212
xtcedef.write_xml(outpath)
1313
assert outpath.exists()
1414

15-
assert space_packet_parser.load_xtce(outpath) == xtcedef
15+
assert spp.load_xtce(outpath) == xtcedef
16+
17+
18+
def test_create_packet_list(jpss_test_data_dir):
19+
"""Test directly creating a list of Packets from a data file and a definition"""
20+
jpss_packets = jpss_test_data_dir / "J01_G011_LZ_2021-04-09T00-00-00Z_V01.DAT1"
21+
jpss_xtce = jpss_test_data_dir / "jpss1_geolocation_xtce_v1.xml"
22+
23+
# Single file
24+
packet_list = spp.create_packet_list(jpss_packets, jpss_xtce)
25+
assert len(packet_list) == 7200
26+
assert packet_list[0]["PKT_APID"] == 11
27+
assert packet_list[-1]["PKT_APID"] == 11
28+
29+
# Multiple files
30+
packet_list = spp.create_packet_list([jpss_packets, jpss_packets], jpss_xtce)
31+
assert len(packet_list) == 14400
32+
assert packet_list[0]["PKT_APID"] == 11
33+
assert packet_list[-1]["PKT_APID"] == 11

0 commit comments

Comments
 (0)