1 """Internal decoding routines."""
2 from __future__
import division
3 from collections
import namedtuple
9 from .constants
import (
11 PRIMARY_HEADER_NUM_BYTES,
14 __author__ =
"Daniel da Silva <mail@danieldasilva.org>"
17 def _get_packet_total_bytes(primary_header_bytes):
18 """Parse the number of bytes in a packet from the bytes associated
19 with a packet's primary header.
23 primary_header_bytes : bytes
24 Bytes associated with the packet primary header, of length
25 `ccsdspy.constants.PRIMARY_HEADER_NUM_BYTES`.
30 Total number of bytes in the packet, including the primary header.
35 The number of bytes in the supplied argument is too short. It must be
36 of length `ccsdspy.constants.PRIMARY_HEADER_NUM_BYTES`.
38 if len(primary_header_bytes) != PRIMARY_HEADER_NUM_BYTES:
40 f
"Primary header byte sequence must be {PRIMARY_HEADER_NUM_BYTES} bytes long"
44 primary_header_byte5 = primary_header_bytes[4]
45 primary_header_byte6 = primary_header_bytes[5]
49 num_bytes = primary_header_byte5 << BITS_PER_BYTE
50 num_bytes += primary_header_byte6
52 num_bytes += PRIMARY_HEADER_NUM_BYTES
57 def _get_packet_apid(primary_header_bytes):
58 """Parse the APID of a packet from the bytes associated
59 with a packet's primary header.
63 primary_header_bytes : bytes
64 Bytes associated with the packet primary header, of length
65 `ccsdspy.constants.PRIMARY_HEADER_NUM_BYTES`.
70 The number of bytes in the supplied argument is too short. It must be
71 of length `ccsdspy.constants.PRIMARY_HEADER_NUM_BYTES`.
73 if len(primary_header_bytes) != PRIMARY_HEADER_NUM_BYTES:
75 f
"Primary header byte sequence must be {PRIMARY_HEADER_NUM_BYTES} bytes long"
79 primary_header_byte1 = primary_header_bytes[0]
80 primary_header_byte2 = primary_header_bytes[1]
84 apid = primary_header_byte1 << BITS_PER_BYTE
85 apid += primary_header_byte2
91 def _decode_fixed_length(file_bytes, fields):
92 """Decode a fixed length packet stream of a single APID.
97 A NumPy array of uint8 type, holding the bytes of the file to decode.
98 fields : list of ccsdspy.PacketField
99 A list of fields, including the secondary header but excluding the
104 dictionary mapping field names to NumPy arrays, stored in the same order as
105 the fields array passed.
109 packet_nbytes = _get_packet_total_bytes(file_bytes[:PRIMARY_HEADER_NUM_BYTES])
110 body_nbytes = sum(field._bit_length
for field
in fields) // BITS_PER_BYTE
111 counter_start = max(0, (packet_nbytes - body_nbytes) * BITS_PER_BYTE)
112 counter = counter_start
116 for i, field
in enumerate(fields):
117 if i == 0
and field._bit_offset
is not None:
119 bit_offset[field._name] = field._bit_offset
120 counter = field._bit_offset + field._bit_length
121 elif field._bit_offset
is None:
124 bit_offset[field._name] = counter
125 counter += field._bit_length
126 elif field._bit_offset < counter:
129 bit_offset[field._name] = field._bit_offset
131 counter = max(field._bit_offset + field._bit_length, counter)
132 elif field._bit_offset >= counter:
135 bit_offset[field._name] = field._bit_offset
136 counter = field._bit_offset + field._bit_length
139 f
"Unexpected case: could not compare"
140 f
" bit_offset {field._bit_offset} with "
141 f
"counter {counter} for field {field._name}"
144 if all(field._bit_offset
is None for field
in fields):
145 assert counter == packet_nbytes * BITS_PER_BYTE,
"Field definition != packet length"
146 elif counter > packet_nbytes * BITS_PER_BYTE:
147 body_bits = sum(field._bit_length
for field
in fields)
150 "Packet definition larger than packet length"
151 f
" by {counter-(packet_nbytes*BITS_PER_BYTE)} bits"
152 f
" (packet length in file is {packet_nbytes*BITS_PER_BYTE} bits, defined fields are {body_bits} bits)"
158 FieldMeta = namedtuple(
"Meta", [
"nbytes_file",
"start_byte_file",
"nbytes_final",
"np_dtype"])
162 nbytes_file = np.ceil(field._bit_length / BITS_PER_BYTE).astype(int)
163 nbytes_final = {3: 4, 5: 8, 6: 8, 7: 8}.get(nbytes_file, nbytes_file)
164 start_byte_file = bit_offset[field._name] // BITS_PER_BYTE
170 byte_order_symbol =
"<" if field._byte_order ==
"little" else ">"
172 "uint":
">u%d" % nbytes_final,
173 "int":
">i%d" % nbytes_final,
174 "fill":
"S%d" % nbytes_final,
175 "float":
"%sf%d" % (byte_order_symbol, nbytes_final),
176 "str":
"S%d" % nbytes_final,
179 field_meta[field] = FieldMeta(nbytes_file, start_byte_file, nbytes_final, np_dtype)
184 extra_bytes = file_bytes.size % packet_nbytes
187 file_bytes = file_bytes[:-extra_bytes]
189 packet_count = file_bytes.size // packet_nbytes
196 meta = field_meta[field]
197 arr = np.zeros(packet_count * meta.nbytes_final,
"u1")
198 xbytes = meta.nbytes_final - meta.nbytes_file
200 for i
in range(xbytes, meta.nbytes_final):
201 arr[i :: meta.nbytes_final] = file_bytes[
202 meta.start_byte_file + i - xbytes :: packet_nbytes
204 field_bytes[field] = arr
211 meta = field_meta[field]
212 arr = field_bytes[field]
214 if field._data_type ==
"int":
217 arr.dtype = meta.np_dtype.replace(
"i",
"u")
219 arr.dtype = meta.np_dtype
221 if field._data_type
in (
"int",
"uint"):
222 xbytes = meta.nbytes_final - meta.nbytes_file
225 bit_offset[field._name]
226 + BITS_PER_BYTE * xbytes
227 - BITS_PER_BYTE * meta.start_byte_file
230 bitmask_right = BITS_PER_BYTE * meta.nbytes_final - bitmask_left - field._bit_length
232 bitmask_left, bitmask_right = np.array([bitmask_left, bitmask_right]).astype(
236 bitmask = np.zeros(arr.shape, arr.dtype)
237 bitmask |= (1 <<
int(BITS_PER_BYTE * meta.nbytes_final - bitmask_left)) - 1
238 tmp = np.left_shift([1], bitmask_right)
239 bitmask &= np.bitwise_not(tmp[0] - 1).astype(arr.dtype)
242 arr >>= bitmask_right
244 if field._byte_order ==
"little":
245 arr.byteswap(inplace=
True)
247 if field._data_type ==
"int":
248 arr.dtype = meta.np_dtype
249 sign_bit = (arr >> (field._bit_length - 1)) & 1
252 one = np.zeros_like(arr) + 1
253 stop_bit = arr.itemsize * BITS_PER_BYTE
254 start_bit = field._bit_length
255 mask = ((one << (start_bit - one)) - one) ^ ((one << stop_bit) - one)
256 arr |= sign_bit * mask
258 field_arrays[field._name] = arr
263 def _decode_variable_length(file_bytes, fields):
264 """Decode a variable length packet stream of a single APID.
269 A NumPy array of uint8 type, holding the bytes of the file to decode.
270 fields : list of ccsdspy.PacketField
271 A list of fields, excluding the
277 A dictionary mapping field names to NumPy arrays, stored in the same order as the fields.
283 while offset < len(file_bytes):
284 packet_starts.append(offset)
285 offset += file_bytes[offset + 4] * 256 + file_bytes[offset + 5] + 7
287 if offset != len(file_bytes):
288 missing_bytes = offset - len(file_bytes)
289 message = f
"File appears truncated - missing {missing_bytes} bytes (or maybe garbage at end)"
290 warnings.warn(message)
292 npackets = len(packet_starts)
297 field_arrays, numpy_dtypes, bit_offsets = _varlength_intialize_field_arrays(fields, npackets)
301 for pkt_num, packet_start
in enumerate(packet_starts):
302 packet_nbytes = file_bytes[packet_start + 4] * 256 + file_bytes[packet_start + 5] + 7
303 bit_offsets_cur = bit_offsets.copy()
309 for i, field
in enumerate(fields):
312 if field._array_shape ==
"expand":
313 footer_bits = sum(field._bit_length
for fld
in fields[i + 1 :])
314 bit_length = packet_nbytes * BITS_PER_BYTE - footer_bits - offset_counter
315 elif isinstance(field._array_shape, str):
317 bit_length = field_arrays[field._array_shape][pkt_num] * field._bit_length
319 bit_length = field._bit_length
321 bit_lengths_cur[field._name] = bit_length
324 if field._name
not in bit_offsets_cur:
325 bit_offsets_cur[field._name] = offset_counter
327 offset_history.append(offset_counter)
328 offset_counter += bit_length
332 field_raw_data =
None
333 if bit_offsets_cur[field._name] < 0:
336 packet_start + packet_nbytes + bit_offsets_cur[field._name] // BITS_PER_BYTE
340 start_byte = packet_start + bit_offsets_cur[field._name] // BITS_PER_BYTE
342 if isinstance(field._array_shape, str):
343 stop_byte = start_byte + bit_lengths_cur[field._name] // BITS_PER_BYTE
344 field_raw_data = file_bytes[start_byte:stop_byte]
348 bit_offset = bit_offsets_cur[field._name]
350 (bit_offset + field._bit_length - 1) // BITS_PER_BYTE
351 - bit_offset // BITS_PER_BYTE
355 nbytes_final = {3: 4, 5: 8, 6: 8, 7: 8}.get(nbytes_file, nbytes_file)
356 xbytes = nbytes_final - nbytes_file
357 field_raw_data = np.zeros(nbytes_final,
"u1")
359 for i
in range(xbytes, nbytes_final):
360 idx = start_byte + i - xbytes
361 field_raw_data[i] = file_bytes[idx]
365 if field._data_type ==
"int":
368 field_raw_data.dtype = numpy_dtypes[field._name].replace(
"i",
"u")
370 field_raw_data.dtype = numpy_dtypes[field._name]
372 if field._data_type
in (
"uint",
"int"):
373 if not isinstance(field._array_shape, str):
374 last_byte = start_byte + nbytes_file
375 end_last_parent_byte = last_byte * BITS_PER_BYTE
377 b = bit_offsets_cur[field._name]
379 b = packet_nbytes * BITS_PER_BYTE + bit_offsets_cur[field._name]
381 last_occupied_bit = packet_start * BITS_PER_BYTE + b + bit_length
382 left_bits_before_shift = b % BITS_PER_BYTE
383 right_shift = end_last_parent_byte - last_occupied_bit
385 assert right_shift >= 0, f
"right_shift={right_shift}, {field}"
387 if left_bits_before_shift > 0:
389 "1" * ((nbytes_file * BITS_PER_BYTE) - left_bits_before_shift), 2
391 field_raw_data &= mask
394 field_raw_data >>= right_shift
396 if field._byte_order ==
"little":
397 field_raw_data.byteswap(inplace=
True)
399 if field._data_type ==
"int":
400 field_raw_data.dtype = numpy_dtypes[field._name]
401 sign_bit = (field_raw_data >> (field._bit_length - 1)) & 1
405 one = np.zeros_like(field_raw_data) + 1
406 stop_bit = field_raw_data.itemsize * BITS_PER_BYTE
407 start_bit = field._bit_length
408 mask = ((one << (start_bit - one)) - one) ^ ((one << stop_bit) - one)
410 field_raw_data |= mask
413 if isinstance(field._array_shape, str):
414 field_arrays[field._name][pkt_num] = field_raw_data
416 field_arrays[field._name][pkt_num] = field_raw_data[0]
421 def _varlength_intialize_field_arrays(fields, npackets):
423 Initialize output dictionary of field arrays, their dtypes, and the offsets
424 that can be determined before parsing each packet.
426 Expanding fields will be an array of dtype=object (jagged array), which will be
427 an array refrence at each index. Non-expanding fields are the matched to the most
432 fields : list of ccsdspy.PacketField
433 A list of fields, including the secondary header but excluding the
437 Number of packets in the file
441 field_arrays : dict, str to array
442 Dictionary of initialized field arrays, mapping string field name to numpy array
443 numpy_dtypes : dict, str to numpy dtype
444 Dictionary of datatypes for the final field arrays, mapping string field names
446 bit_offsets : dict, str to int/None
447 Dictionary of bit offsets that can be determined before parsing each packet. Maps
448 string field names to integers or None
456 for i, field
in enumerate(fields):
457 if isinstance(field._array_shape, str):
459 elif field._bit_offset
is None:
460 bit_offsets[field._name] = counter
461 counter += field._bit_length
463 bit_offsets[field._name] = field._bit_offset
464 counter = max(field._bit_offset + field._bit_length, counter)
466 for i, field
in enumerate(fields):
467 if isinstance(field._array_shape, str):
470 if last_var_idx
is not None:
472 footer_fields = fields[last_var_idx + 1 :]
473 for i, field
in enumerate(reversed(footer_fields)):
474 bit_offsets[field._name] = counter - field._bit_length
475 counter -= field._bit_length
483 for i, field
in enumerate(fields):
485 if isinstance(field._array_shape, str):
486 nbytes_file[field._name] = field._bit_length // BITS_PER_BYTE
488 if i > 0
and isinstance(fields[i - 1]._array_shape, str):
491 nbytes_file[field._name] = math.ceil(field._bit_length / BITS_PER_BYTE)
493 bit_offset = bit_offsets[field._name]
494 nbytes_file[field._name] = (
495 (bit_offset + field._bit_length - 1) // BITS_PER_BYTE
496 - bit_offset // BITS_PER_BYTE
502 nbytes_final = {3: 4, 5: 8, 6: 8, 7: 8}.get(
503 nbytes_file[field._name], nbytes_file[field._name]
510 byte_order_symbol =
"<" if field._byte_order ==
"little" else ">"
512 "uint":
">u%d" % nbytes_final,
513 "int":
">i%d" % nbytes_final,
514 "fill":
"S%d" % nbytes_final,
515 "float":
"%sf%d" % (byte_order_symbol, nbytes_final),
516 "str":
"S%d" % nbytes_final,
519 numpy_dtypes[field._name] = np_dtype
521 if isinstance(field._array_shape, str):
522 field_arrays[field._name] = np.zeros(npackets, dtype=object)
524 field_arrays[field._name] = np.zeros(npackets, dtype=np_dtype)
526 return field_arrays, numpy_dtypes, bit_offsets