Working with Chapter10 data in Python

Chapter 10 files are widely-used in aerospace for recording and exchanging time-synchronized telemetry data, often in flight test and mission-oriented environments.

Introduced by the Range Commanders Council (RCC) in 1997, this format encapsulates a variety of data types (such as video, audio, instrumentation, and bus data) into a single stream. It supports real-time data acquisition, playback, and analysis, making it common in applications like flight test evaluation, mission debriefing, and simulation. The Chapter 10 standard incorporates IRIG (Inter-range Instrumentation Group) time codes for synchronizing and timestamping data across various channels. Its modular design ensures adaptability to evolving data capture requirements and technology - so it will likely remain a standard for years to come.

This guide gives an overview for working with Chapter10 data in Python, then uploading it to the Nominal platform.

If your organization regularly works with Chapter10 data, the Nominal platform offers customizable, no-code upload forms and pipelines specifically for this format.

Please contact us for a demo!

Connect to Nominal

Get your Nominal API token from your User settings page.

See the Quickstart for more details on connecting to Nominal from Python.

1import nominal.nominal as nm
2
3nm._config.set_token(
4 url = 'https://api.gov.nominal.io/api',
5 token = '* * *' # Replace with your Access Token from
6 # https://app.gov.nominal.io/settings/user?tab=tokens
7)
If you’re not sure whether your company has a Nominal tenant, please reach out to us.

Download Chapter10 data

You’ll need huggingface_hub, pychapter10, and a few other libraries for this guide. You can install them all with the following command:

$pip install huggingface_hub polars nominal pychapter10

We’ll use the Hugging Face Hub to download sample Chapter10 files in Python. You can also download them manually if you prefer. These sample files originated from the PyChapter project.

1from huggingface_hub import hf_hub_download
2
3repo_id = "nominal-io/chapter10"
4filename = "discrete.c10"
5
6file_path = hf_hub_download(
7 repo_id=repo_id,
8 filename=filename,
9 repo_type="dataset"
10)

file_path now contains the path to our Chapter10 file.

Inspect “discrete.c10”

First, let’s import the libraries we’ll need for this guide.

1import polars as pl
2from chapter10 import C10
3import pprint

Chapter10 files are composed of “packets.” Each packet contains metadata fields and a “data” field with the actual packet data. You can inspect these packets manually by iterating through them:

1for packet in C10(file_path):
2 pprint.pprint(packet.__dict__)
{'_messages': [],
'buffer': <_io.BytesIO object at 0x174a60db0>,
'channel_id': 0,
'configuration_change': 0,
'data': b'COMMENT: Original Recording File - 1553-AR429-64DISC-IRIG11.ch10'
b';\r\nCOMMENT: RSN Name - File0090;\r\nCOMMENT: ----------- Channel S'
b'ummary -------------;\r\nCOMMENT: 55 channels (39 enabled);\r\nCOMME'
b'NT: Id Data Type Data Source Id;\r\nCOMMENT: ---- --------- '
b'---------------;\r\nCOMMENT: 1 Time TIME01;\r\nCOMMENT: 1'
b'0 1553 M05;\r\nCOMMENT: 11 1553 M06;\r\nCOMMENT: 1'
...

With manual inspection, it’s difficult to read the data payload without decoding and displaying it line-by-line.

Parse the 1st packet

Let’s make the data payload in the first packet easier to read. We’ll isolate the first packet with next(iter(packets)), decode it with decode('utf-8'), and print it line-by-line.

1def decode_first_packet_data(file_path):
2 packets = C10(file_path)
3 first_packet = next(iter(packets))
4 decoded_first_packet = first_packet.data.decode('utf-8').replace('\r\n', '\n')
5 print(decoded_first_packet)
6
7decode_first_packet_data(file_path)
G\PN:Heim GSS-100;
G\TA:TMATS generated by GSSServer;
G\106:07;
G\DSI\N:1;
G\DSI-1:DATASOURCE;
G\DST-1:OTH;
G\OD:No Date;
G\UD:No Date;
G\POC\N:1;
G\POC1-1:GSSServer;
G\COM:No Comment - TMATS generated by H2TMATS Version: 1.01.00 Date: Jan 29 2009 09:28:15;
...

Now it’s easier to read a single packet’s data line-by-line.

Given the name of the file (“discrete.c10”) and the channel names (“DISC01” and “DISC02”), this file appears to specifically monitor binary events such as switches, states, or triggers.

Summarize packets in a table

Let’s iterate through the packets and summarize them into an easy-to-read table. To do this, we’ll use Polars - a popular Python library for working with tabular data. Tables in Polars are referred to as “DataFrames” and often used for ad hoc data analysis.

1def summarize_ch10_packets_in_table(file_path):
2 data = []
3
4 schema = dict(
5 ts = pl.Datetime,
6 channel_id = pl.Int64,
7 data_type = pl.Int64,
8 packet_type = pl.Utf8,
9 data = pl.Binary
10 )
11
12 for packet in C10(file_path):
13 packet_type = str(type(packet)) \
14 .strip("<class 'chapter10") \
15 .strip("'>")
16 row = dict(ts = packet.get_time(),
17 channel_id = packet.channel_id,
18 data_type = packet.data_type,
19 packet_type = packet_type,
20 data = ''
21 )
22 if 'data' in packet.__dict__:
23 row['data'] = packet.data[:20] # truncate to 20 bytes
24
25 data.append(row)
26
27 df = pl.DataFrame(data, schema = schema)
28
29 return df
1df = summarize_ch10_packets_in_table(file_path)
2df
tschannel_iddata_typepacket_typedata
datetime[μs]i64i64strbinary
2024-12-12 18:04:27.12984501”.computer.ComputerF1”b”G\PN:Heim\x20GSS-100;\x0d\x0a”
2024-04-06 09:03:06117”.time.TimeF1”b""
2024-04-06 09:03:0600”.computer.ComputerF0”b”heim\x00\x10\xfe\xfe\xff\xfe”
2024-04-06 09:03:05.9109417356”.arinc429.ARINC429F0”b""
2024-04-06 09:03:05.9114087456”.arinc429.ARINC429F0”b""
2024-04-06 09:03:06.0199839225”.ms1553.MS1553F1”b""
2024-04-06 09:03:06.0199839325”.ms1553.MS1553F1”b""
2024-04-06 09:03:06.0199839425”.ms1553.MS1553F1”b""
2024-04-06 09:03:05.993928519”.pcm.PCMF1”b""
2024-04-06 09:03:05.8160524464”.video.VideoF0”b""

Upload to Nominal

Once the data’s in a Polars DataFrame, it’s simple to upload to the Nominal platform:

1import nominal.nominal as nm
2
3nm.upload_polars(df, name = "chapter10-discrete")

See the upload_polars function reference for more details.

Inspect PCM data

PCM (Pulse Code Modulation) refers to a method of digitally encoding analog signals, such as instrumentation or telemetry data, for efficient transmission and storage. PCM converts the continuous analog signal into discrete digital samples at regular intervals, representing the amplitude as binary data.

In Ch10, PCM streams are often used to encapsulate telemetry signals from onboard sensors and systems, enabling precise and synchronized playback during analysis. PCM is a fundamental data format in Ch10 for handling time-synchronized analog-to-digital conversions within flight tests and other aerospace applications.

Let’s inspect the pcm.c10 sample file in the same way we did for discrete.c10. As before, if you don’t want to use huggingface_hub, you can download the file manually from Nominal’s Hugging Face.

1from huggingface_hub import hf_hub_download
2
3repo_id = "nominal-io/chapter10"
4filename = "pcm.c10"
5
6file_path = hf_hub_download(
7 repo_id=repo_id,
8 filename=filename,
9 repo_type="dataset"
10)

pcm.c10 is now at the path file_path.

Iterate through packets

1for packet in C10(file_path):
2 pprint.pprint(packet.__dict__)
{'_messages': [],
'buffer': <_io.BytesIO object at 0x176c9db20>,
'channel_id': 0,
'configuration_change': 0,
'data': b'G\\PN:Heim GSS-100;\r\nG\\TA:TMATS generated by GSSServer;\r\nG\\10'
b'6:07;\r\nG\\DSI\\N:1;\r\nG\\DSI-1:DATASOURCE;\r\nG\\DST-1:OTH;'
b'\r\nG\\OD:No Date;\r\nG\\UD:No Date;\r\nG\\POC\\N:1;\r\nG\\POC1-1'
...

As before, it’s difficult to read the data payload without decoding and displaying it line-by-line.

Parse the 1st packet

Let’s parse the first packet of data so that we can read it line-by-line.

1decode_first_packet_data(file_path)
G\PN:Heim GSS-100;
G\TA:TMATS generated by GSSServer;
G\106:07;
G\DSI\N:1;
G\DSI-1:DATASOURCE;
...

One interesting note is the “Heim GSS-100” part number in the first line - a popular ground station model. pcm.c10 was likely downloaded from one of these ground station instruments.

Summarize packets in a table

These tables are useful for quickly scanning Chapter10 packet content.

1df = summarize_ch10_packets_in_table(file_path)
2df
tschannel_iddata_typepacket_typedata
datetime[μs]i64i64strbinary
2024-12-12 18:07:07.97369201”.computer.ComputerF1”b”G\PN:Heim\x20GSS-100;\x0d\x0a”
2024-04-06 09:03:06117”.time.TimeF1”b""
2024-04-06 09:03:0600”.computer.ComputerF0”b”heim\x00\x10\xfe\xfe\xff\xfe”
2024-04-06 09:03:05.9109417356”.arinc429.ARINC429F0”b""
2024-04-06 09:03:05.9114087456”.arinc429.ARINC429F0”b""
2024-04-06 09:03:06.0199839225”.ms1553.MS1553F1”b""
2024-04-06 09:03:06.0199839325”.ms1553.MS1553F1”b""
2024-04-06 09:03:06.0199839425”.ms1553.MS1553F1”b""
2024-04-06 09:03:05.993928519”.pcm.PCMF1”b""
2024-04-06 09:03:05.8160524464”.video.VideoF0”b""

Upload to Nominal

As before, once the data’s in a Polars DataFrame, automating its upload to Nominal is a one-liner:

1import nominal.nominal as nm
2
3nm.upload_polars(df, name = "chapter10-pcm")

If your team regularly works with Chapter10 data, please don’t hesitate to contact us for an in-depth demo of all of Nominal’s advanced Chapter10 capabilities.

Built with