space-packet-parser 4.2.0__tar.gz → 5.0.0__tar.gz

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.
@@ -0,0 +1,33 @@
1
+ cff-version: 1.2.0
2
+ title: 'space_packet_parser'
3
+ type: software
4
+ version: '5.0.0'
5
+ description: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
6
+ license: BSD-3-Clause
7
+ abstract: The Space Packet Parser Python library is a generalized, configurable packet decoding library for CCSDS telemetry
8
+ packets based on the XTCE standard for packet structure definitions. It supports complex and polymorphic
9
+ packet structures, using the XTCE UML model to represent dynamic inheritance structures and conditionals
10
+ based on previously parsed data fields. The core functionality of the library is the
11
+ configuration of an XtcePacketDefinition object from a static XTCE XML document. The configured definition
12
+ object can then iterate over binary data, parsing and yielding parsed Packet objects containing the decoded
13
+ packet field values in a generator pattern. The binary data may originate from an in-memory binary object,
14
+ a buffered file reader opened in binary mode, or a python socket object; in every case, a small buffer is
15
+ used to read chunks of data to reduce memory footprint.
16
+ The space_packet_parser library supports robust error handling, is capable of handling malformed
17
+ packet structures, and can dynamically parse muxed APID packet streams.
18
+ authors:
19
+ - email: gavin.medley@lasp.colorado.edu
20
+ name: Gavin Medley
21
+ orcid: "0000-0002-3520-9715"
22
+ - email: michael.chambliss@lasp.colorado.edu
23
+ name: Michael Chambliss
24
+ orcid: "0009-0003-7493-0542"
25
+ - email: greg.lucas@lasp.colorado.edu
26
+ name: Greg Lucas
27
+ orcid: "0000-0003-1331-1863"
28
+ maintainers:
29
+ - email: gavin.medley@lasp.colorado.edu
30
+ name: Gavin Medley
31
+ orcid: "0000-0002-3520-9715"
32
+ repository-code: "https://github.com/medley56/space_packet_parser"
33
+ url: "https://space-packet-parser.readthedocs.io"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: space_packet_parser
3
- Version: 4.2.0
3
+ Version: 5.0.0
4
4
  Summary: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
5
5
  Home-page: https://space-packet-parser.readthedocs.io
6
6
  License: BSD-3-Clause
@@ -9,28 +9,26 @@ Author: Gavin Medley
9
9
  Author-email: gavin.medley@lasp.colorado.edu
10
10
  Maintainer: Gavin Medley
11
11
  Maintainer-email: gavin.medley@lasp.colorado.edu
12
- Requires-Python: >=3.8
12
+ Requires-Python: >=3.9
13
13
  Classifier: License :: OSI Approved :: BSD License
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
- Requires-Dist: bitstring (>=4.0.1)
19
+ Requires-Dist: lxml (>=4.8.0)
21
20
  Project-URL: Repository, https://github.com/medley56/space_packet_parser
22
21
  Description-Content-Type: text/markdown
23
22
 
24
23
  # Space Packet Parser
25
24
 
26
- ----------
27
- [![Test Status](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml/badge.svg)](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml)
25
+ [![Test Status](https://github.com/medley56/space_packet_parser/actions/workflows/pr_tests.yml/badge.svg)](https://github.com/medley56/space_packet_parser/actions/workflows/pr_tests.yml)
28
26
  [![Doc Status](https://readthedocs.org/projects/space-packet-parser/badge/?version=latest)](https://readthedocs.org/projects/space-packet-parser/)
29
27
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7735001.svg)](https://doi.org/10.5281/zenodo.7735001)
30
28
 
31
29
  Documentation: [https://space-packet-parser.readthedocs.io/en/latest/](https://space-packet-parser.readthedocs.io/en/latest/)
32
30
 
33
- Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
31
+ Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE packet structure definition.
34
32
  It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
35
33
  XTCE telemetry packet specification.
36
34
 
@@ -1,13 +1,12 @@
1
1
  # Space Packet Parser
2
2
 
3
- ----------
4
- [![Test Status](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml/badge.svg)](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml)
3
+ [![Test Status](https://github.com/medley56/space_packet_parser/actions/workflows/pr_tests.yml/badge.svg)](https://github.com/medley56/space_packet_parser/actions/workflows/pr_tests.yml)
5
4
  [![Doc Status](https://readthedocs.org/projects/space-packet-parser/badge/?version=latest)](https://readthedocs.org/projects/space-packet-parser/)
6
5
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7735001.svg)](https://doi.org/10.5281/zenodo.7735001)
7
6
 
8
7
  Documentation: [https://space-packet-parser.readthedocs.io/en/latest/](https://space-packet-parser.readthedocs.io/en/latest/)
9
8
 
10
- Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
9
+ Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE packet structure definition.
11
10
  It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
12
11
  XTCE telemetry packet specification.
13
12
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "space_packet_parser"
3
- version = "4.2.0"
3
+ version = "5.0.0"
4
4
  description = "A CCSDS telemetry packet decoding library based on the XTCE packet format description standard."
5
5
  license = "BSD-3-Clause"
6
6
  readme = "README.md"
@@ -35,10 +35,11 @@ keywords = [
35
35
  ]
36
36
 
37
37
  [tool.poetry.dependencies]
38
- python = ">=3.8"
39
- bitstring = ">=4.0.1"
38
+ python = ">=3.9"
39
+ lxml = ">=4.8.0"
40
40
 
41
41
  [tool.poetry.group.dev.dependencies]
42
+ pycodestyle = "*"
42
43
  pylint = "*"
43
44
  pytest = "*"
44
45
  pytest-randomly = "*"
@@ -0,0 +1,361 @@
1
+
2
+ """Calibrator definitions"""
3
+ from abc import ABCMeta, abstractmethod
4
+ from collections import namedtuple
5
+ from typing import List, Union
6
+
7
+ import lxml.etree as ElementTree
8
+
9
+ from space_packet_parser.exceptions import CalibrationError
10
+ from space_packet_parser import comparisons
11
+
12
+
13
+ class Calibrator(comparisons.AttrComparable, metaclass=ABCMeta):
14
+ """Abstract base class for XTCE calibrators"""
15
+
16
+ @classmethod
17
+ @abstractmethod
18
+ def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'Calibrator':
19
+ """Abstract classmethod to create a default_calibrator object from an XML element.
20
+
21
+ Parameters
22
+ ----------
23
+ element : ElementTree.Element
24
+ XML element
25
+ ns : dict
26
+ XML namespace dict
27
+
28
+ Returns
29
+ -------
30
+ cls
31
+ """
32
+ return NotImplemented
33
+
34
+ @abstractmethod
35
+ def calibrate(self, uncalibrated_value: Union[int, float]) -> Union[int, float]:
36
+ """Takes an integer-encoded or float-encoded value and returns a calibrated version.
37
+
38
+ Parameters
39
+ ----------
40
+ uncalibrated_value : Union[int, float]
41
+ The uncalibrated, raw encoded value
42
+
43
+ Returns
44
+ -------
45
+ : Union[int, float]
46
+ Calibrated value
47
+ """
48
+ raise NotImplementedError
49
+
50
+
51
+ SplinePoint = namedtuple('SplinePoint', ['raw', 'calibrated'])
52
+
53
+
54
+ class SplineCalibrator(Calibrator):
55
+ """<xtce:SplineCalibrator>"""
56
+ _order_mapping = {'zero': 0, 'first': 1, 'second': 2, 'third': 3}
57
+
58
+ def __init__(self, points: list, order: int = 0, extrapolate: bool = False):
59
+ """Constructor
60
+
61
+ Parameters
62
+ ----------
63
+ points : list
64
+ List of SplinePoint objects. These points are sorted by their raw values on instantiation.
65
+ order : int
66
+ Spline order. Only zero and first order splines are supported.
67
+ extrapolate : bool
68
+ Whether or not to allow extrapolation outside the bounds of the spline points. If False, raises an
69
+ error when calibrate is called for a query point outside the bounds of the spline points.
70
+ """
71
+ if order > 1:
72
+ raise NotImplementedError("Spline calibrators of order > 1 are not implemented. Consider contributing "
73
+ "if you need this functionality. It does not appear to be commonly used but "
74
+ "it probably would not be too hard to implement.")
75
+ self.order = order
76
+ self.points = sorted(points, key=lambda point: point.raw) # Sort points before storing
77
+ self.extrapolate = extrapolate
78
+
79
+ @classmethod
80
+ def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'SplineCalibrator':
81
+ """Create a spline default_calibrator object from an <xtce:SplineCalibrator> XML element."""
82
+ point_elements = element.findall('xtce:SplinePoint', ns)
83
+ spline_points = [
84
+ SplinePoint(raw=float(p.attrib['raw']), calibrated=float(p.attrib['calibrated']))
85
+ for p in point_elements
86
+ ]
87
+ order = int(cls._order_mapping[element.attrib['order']]) if 'order' in element.attrib else 0
88
+ extrapolate = element.attrib['extrapolate'].lower() == 'true' if 'extrapolate' in element.attrib else False
89
+ return cls(order=order, points=spline_points, extrapolate=extrapolate)
90
+
91
+ def calibrate(self, uncalibrated_value: float) -> float:
92
+ """Take an integer-encoded value and returns a calibrated version according to the spline points.
93
+
94
+ Parameters
95
+ ----------
96
+ uncalibrated_value : float
97
+ Query point.
98
+
99
+ Returns
100
+ -------
101
+ : float
102
+ Calibrated value
103
+ """
104
+ if self.order == 0:
105
+ return self._zero_order_spline_interp(uncalibrated_value)
106
+ if self.order == 1:
107
+ return self._first_order_spline_interp(uncalibrated_value)
108
+ raise NotImplementedError(f"SplineCalibrator is not implemented for spline order {self.order}.")
109
+
110
+ def _zero_order_spline_interp(self, query_point: float) -> float:
111
+ """Abstraction for zero order spline interpolation. If extrapolation is set to a truthy value, we use
112
+ the nearest point to extrapolate outside the range of the given spline points. Within the range of spline
113
+ points, we use nearest lower point interpolation.
114
+
115
+ Parameters
116
+ ----------
117
+ query_point : float
118
+ Query point.
119
+
120
+ Returns
121
+ -------
122
+ : float
123
+ Calibrated value.
124
+ """
125
+ x = [float(p.raw) for p in self.points]
126
+ y = [float(p.calibrated) for p in self.points]
127
+ if min(x) <= query_point <= max(x):
128
+ first_greater = [p.raw > query_point for p in self.points].index(True)
129
+ return y[first_greater - 1]
130
+ if query_point > max(x) and self.extrapolate:
131
+ return y[-1]
132
+ if query_point < min(x) and self.extrapolate:
133
+ return y[0]
134
+ raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
135
+ f"{query_point} falls outside the range of spline points {self.points}")
136
+
137
+ def _first_order_spline_interp(self, query_point: float) -> float:
138
+ """Abstraction for first order spline interpolation. If extrapolation is set to a truthy value, we use the
139
+ end points to make a linear function and use it to extrapolate.
140
+
141
+ Parameters
142
+ ----------
143
+ query_point : float
144
+ Query point.
145
+
146
+ Returns
147
+ -------
148
+ float
149
+ Calibrated value.
150
+ """
151
+
152
+ def linear_func(xq: float, x0: float, x1: float, y0: float, y1: float) -> float:
153
+ """Evaluate a linear function through points (x0, y0), (x1, y1) at point xq
154
+
155
+ Parameters
156
+ ----------
157
+ xq : float
158
+ x0 : float
159
+ x1 : float
160
+ y0 : float
161
+ y1 : float
162
+
163
+ Returns
164
+ -------
165
+ yq : float
166
+ Interpolated point
167
+ """
168
+ slope = (y1 - y0) / (x1 - x0)
169
+ return (slope * (xq - x0)) + y0
170
+
171
+ x = [p.raw for p in self.points]
172
+ y = [p.calibrated for p in self.points]
173
+ if min(x) <= query_point <= max(x):
174
+ first_greater = [p.raw > query_point for p in self.points].index(True)
175
+ return linear_func(query_point,
176
+ x[first_greater - 1], x[first_greater],
177
+ y[first_greater - 1], y[first_greater])
178
+ if query_point > max(x) and self.extrapolate:
179
+ return linear_func(query_point, x[-2], x[-1], y[-2], y[-1])
180
+ if query_point < min(x) and self.extrapolate:
181
+ return linear_func(query_point, x[0], x[1], y[0], y[1])
182
+ raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
183
+ f"{query_point} falls outside the range of spline points {self.points}")
184
+
185
+
186
+ PolynomialCoefficient = namedtuple('PolynomialCoefficient', ['coefficient', 'exponent'])
187
+
188
+
189
+ class PolynomialCalibrator(Calibrator):
190
+ """<xtce:PolynomialCalibrator>"""
191
+
192
+ def __init__(self, coefficients: list):
193
+ """Constructor
194
+
195
+ Parameters
196
+ ----------
197
+ coefficients : list
198
+ List of PolynomialCoefficient objects that define the polynomial.
199
+ """
200
+ self.coefficients = coefficients # Coefficients should be a list of PolynomialCoefficients
201
+
202
+ @classmethod
203
+ def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'PolynomialCalibrator':
204
+ """Create a polynomial default_calibrator object from an <xtce:PolynomialCalibrator> XML element.
205
+
206
+ Parameters
207
+ ----------
208
+ element : ElementTree.Element
209
+ <xtce:PolynomialCalibrator> XML element
210
+ ns : dict
211
+ Namespace dict
212
+
213
+ Returns
214
+ -------
215
+
216
+ """
217
+ terms = element.findall('xtce:Term', ns)
218
+ coefficients = [
219
+ PolynomialCoefficient(coefficient=float(term.attrib['coefficient']), exponent=int(term.attrib['exponent']))
220
+ for term in terms
221
+ ]
222
+ return cls(coefficients=coefficients)
223
+
224
+ def calibrate(self, uncalibrated_value: float) -> float:
225
+ """Evaluate the polynomial defined by object coefficients at the specified uncalibrated point.
226
+
227
+ Parameters
228
+ ----------
229
+ uncalibrated_value : float
230
+ Query point.
231
+
232
+ Returns
233
+ -------
234
+ float
235
+ Calibrated value
236
+ """
237
+ return sum(a * (uncalibrated_value ** n) for a, n in self.coefficients)
238
+
239
+
240
+ class MathOperationCalibrator(Calibrator):
241
+ """<xtce:MathOperationCalibrator>"""
242
+ err_msg = "The MathOperationCalibrator element is not supported in this package but pull requests are welcome!"
243
+
244
+ def __init__(self):
245
+ """Constructor
246
+
247
+ Not implemented.
248
+ """
249
+ raise NotImplementedError(self.err_msg)
250
+
251
+ @classmethod
252
+ def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'MathOperationCalibrator':
253
+ """Create a math operation default_calibrator from an <xtce:MathOperationCalibrator> XML element."""
254
+ raise NotImplementedError(cls.err_msg)
255
+
256
+ def calibrate(self, uncalibrated_value: int):
257
+ """Stub
258
+
259
+ Parameters
260
+ ----------
261
+ uncalibrated_value
262
+
263
+ Returns
264
+ -------
265
+
266
+ """
267
+ raise NotImplementedError(self.err_msg)
268
+
269
+
270
+ class ContextCalibrator(comparisons.AttrComparable):
271
+ """<xtce:ContextCalibrator>"""
272
+
273
+ def __init__(self, match_criteria: list, calibrator: Calibrator):
274
+ """Constructor
275
+
276
+ Parameters
277
+ ----------
278
+ match_criteria : Union[MatchCriteria, list]
279
+ Object representing the logical operations to be performed to determine whether to use this
280
+ default_calibrator. This can be a Comparison, a ComparsonList (a list of Comparison objects),
281
+ a BooleanExpression (not supported), or a CustomAlgorithm (not supported)
282
+ calibrator : Calibrator
283
+ Calibrator to use if match criteria evaluates to True
284
+ """
285
+ self.match_criteria = match_criteria
286
+ self.calibrator = calibrator
287
+
288
+ @staticmethod
289
+ def get_context_match_criteria(element: ElementTree.Element, ns: dict) -> List[comparisons.MatchCriteria]:
290
+ """Parse contextual requirements from a Comparison, ComparisonList, or BooleanExpression
291
+
292
+ Parameters
293
+ ----------
294
+ element : ElementTree.Element
295
+ <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
296
+ ns : dict
297
+ Namespace dict for XML parsing
298
+
299
+ Returns
300
+ -------
301
+ : List[MatchCriteria]
302
+ List of Comparisons that can be evaluated to determine whether this calibrator should be used.
303
+ """
304
+ context_match_element = element.find('xtce:ContextMatch', ns)
305
+ if context_match_element.find('xtce:ComparisonList', ns) is not None:
306
+ return [comparisons.Comparison.from_match_criteria_xml_element(el, ns)
307
+ for el in context_match_element.findall('xtce:ComparisonList/xtce:Comparison', ns)]
308
+ if context_match_element.find('xtce:Comparison', ns) is not None:
309
+ return [comparisons.Comparison.from_match_criteria_xml_element(
310
+ context_match_element.find('xtce:Comparison', ns), ns)]
311
+ if context_match_element.find('xtce:BooleanExpression', ns) is not None:
312
+ return [comparisons.BooleanExpression.from_match_criteria_xml_element(
313
+ context_match_element.find('xtce:BooleanExpression', ns), ns)]
314
+ raise NotImplementedError("ContextCalibrator doesn't contain Comparison, ComparisonList, or BooleanExpression. "
315
+ "This probably means the match criteria is an unsupported type "
316
+ "(CustomAlgorithm).")
317
+
318
+ @classmethod
319
+ def from_context_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'ContextCalibrator':
320
+ """Create a ContextCalibrator object from an <xtce:ContextCalibrator> XML element
321
+
322
+ Parameters
323
+ ----------
324
+ element : ElementTree.Element
325
+ <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
326
+ ns : dict
327
+ Namespace dict for XML parsing
328
+
329
+ Returns
330
+ -------
331
+ : cls
332
+ """
333
+ match_criteria = cls.get_context_match_criteria(element, ns)
334
+
335
+ if element.find('xtce:Calibrator/xtce:SplineCalibrator', ns) is not None:
336
+ calibrator = SplineCalibrator.from_calibrator_xml_element(
337
+ element.find('xtce:Calibrator/xtce:SplineCalibrator', ns), ns)
338
+ elif element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns):
339
+ calibrator = PolynomialCalibrator.from_calibrator_xml_element(
340
+ element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns), ns)
341
+ else:
342
+ raise NotImplementedError(
343
+ "Unsupported default_calibrator type. space_packet_parser only supports Polynomial and Spline"
344
+ "calibrators for ContextCalibrators.")
345
+
346
+ return cls(match_criteria=match_criteria, calibrator=calibrator)
347
+
348
+ def calibrate(self, parsed_value: Union[int, float]) -> Union[int, float]:
349
+ """Wrapper method for the internal `Calibrator.calibrate`
350
+
351
+ Parameters
352
+ ----------
353
+ parsed_value : Union[int, float]
354
+ Uncalibrated value.
355
+
356
+ Returns
357
+ -------
358
+ : Union[int, float]
359
+ Calibrated value
360
+ """
361
+ return self.calibrator.calibrate(parsed_value)