Post-Processing Transformations

Post-processing transformations are done through the Converters API, exposed through the converters module. Using Converters, one can create new fields transformed from another field. Examples including applying calibration curves, replacing enumerated values with strings, or converting your own time definition to a datetime instance. The following converters are built in, and you can write your own converter by extending the Converter class (more on this below). When new fields are transformed from other fields, they are created as new entries in the returned dictionary.

  1. Polynomial Transformation (PolyConverter)

    This applies a polynomial function to each value, using user-defined coefficients.

  2. Linear Transformation (LinearConverter)

    This applies a linear transformation (y = mx + b) to each value, using a user-defined slope and intercept.

  3. Enumerated Values Transformation (EnumConverter)

    This applies a dictionary replacement of integers to strings. For instance, you can use this to replace 0 with "DISABLED", 1 with "ENABLED", and 2 with "STANDBY".

  4. Datetime Transformation (DatetimeConverter)

    This converts fields to datetime instances, computed using offset(s) from a reference time. The offsets can span multiple fields (for example, one a coarse time, and another for a fine time). If the reference time has a timezone, the result will too.

  5. Stringify Bytes Transformation (StringifyBytesConverter)

    This converts byte arrays or multi-byte numbers to strings in numeric representations such as binary, hexadecimal, or octal.

Using Built-In Transformations

An example of using a built in transformation to parse time, apply a linear transformation to a first field, and apply a enumerated values transformation to a secondary field is below.

from ccsdspy import FixedLength, converters

pkt = FixedLength([
    PacketField(name="CoarseTime",  data_type="uint", bit_length=24)
    PacketField(name="FineTime",    data_type="uint", bit_length=48)
    PacketField(name="FirstField",  data_type="uint", bit_length=8),
    PacketField(name="SecondField", data_type="uint", bit_length=8),
])

pkt.add_converted_field(
    ("CoarseTime", "FineTime"),
    "Time_Converted",
    converters.DatetimeConverter(
       since=datetime(1970, 1, 1),
       units=("seconds", "nanoseconds"),
    )
)
pkt.add_converted_field(
    "FirstField",
    "FirstField_Converted",
    converters.LinearConverter(slope=1.2, intercept=0.4)
)
pkt.add_converted_field(
    "SecondField",
    "SecondField_Converted",
    converters.EnumConverter({
        0: "DISABLED",
        1: "ENABLED",
        2: "STANDBY"
    })
)

result = pkt.load("MyCCSDS.bin")

print(result["Time_Converted"])
print(result["FirstField_Converted"])
print(result["SecondField_Converted"])

Creating User-Defined Transformations

Users can create their own user-defined transformations by extending the Converter class and overriding the convert(*field_arrays) function. This function accepts as many arguments argument as input fields were provided when the converter was added.

Below is an example of creating a user-defined transformation to return False if the value is equal to zero, and True if the value is greater than zero.

from ccsdspy import FixedLength, converters

class CustomConverter(converters.Converter):
    def __init__(self):
        pass
    def convert(field_array):
        return (field_array > 0)

pkt = FixedLength([
    PacketField(name="MyField", data_type="uint", bit_length=8)
])
pkt.add_converted_field(
    "MyField",
    "MyField_Converted",
    CustomConverter()
)

result = pkt.load("MyCCSDS.bin")

print(result["MyField_Converted"])