PX4 ULog#
Convert a PX4 ULog flight log into one CSV per topic, align their boot-relative clocks to UTC and upload them to a run.
The examples also need pip install pyulog polars huggingface_hub.
Connect the client#
This guide assumes you have installed the client and setup your API keys.
from nominal.core import NominalClient
client = NominalClient.from_profile("default")
Convert the log to CSV#
This example downloads a sample quadrotor log. pyulog’s ulog2csv writes one CSV per topic next to it:
import subprocess
from pathlib import Path
from huggingface_hub import hf_hub_download
ulog_path = Path(
hf_hub_download(
repo_id="nominal-io/px4-ulog-quadrotor",
filename="PX4-quadrotor-example-log.ulg",
repo_type="dataset",
)
)
subprocess.run(["ulog2csv", ulog_path], check=True)
csv_files = sorted(ulog_path.parent.glob(f"{ulog_path.stem}_*.csv"))
Topic files are named like <log>_vehicle_gps_position_0.csv.
A topic can have several instances, such as battery_status_0 and battery_status_1.
Align the clocks to UTC#
Each topic’s timestamp column counts microseconds since the flight computer booted.
The GPS topic also records time_utc_usec, so one GPS row gives the UTC time of boot:
import polars as pl
from nominal import ts
gps = pl.read_csv(ulog_path.parent / f"{ulog_path.stem}_vehicle_gps_position_0.csv")
anchor = gps.filter(pl.col("time_utc_usec") > 0).row(0, named=True)
boot_us = anchor["time_utc_usec"] - anchor["timestamp"] - anchor["timestamp_time_relative"]
boot_clock = ts.Relative("microseconds", start=boot_us * 1000) # start is in epoch nanoseconds
timestamp_time_relative is the offset of the UTC reading from timestamp, as defined in the PX4 GPS message.
Logs that span several boots need one clock per boot.
Upload the topics to a run#
Create one dataset per topic, upload each instance’s CSV to it, then attach the datasets to a run under the topic name:
datasets = {}
first_us, last_us = [], []
for path in csv_files:
if "timestamp" not in pl.read_csv(path, n_rows=1).columns:
continue # skip topics without a timestamp column
stamps = pl.read_csv(path, columns=["timestamp"])["timestamp"]
first_us.append(stamps.min())
last_us.append(stamps.max())
topic = path.stem.removeprefix(f"{ulog_path.stem}_").rsplit("_", 1)[0] # drop the instance number
if topic not in datasets:
datasets[topic] = client.create_dataset(topic)
datasets[topic].add_tabular_data(path, timestamp_column="timestamp", timestamp_type=boot_clock)
start_us = boot_us + min(first_us)
end_us = boot_us + max(last_us)
run = client.create_run("PX4 quadrotor flight", start=start_us * 1000, end=end_us * 1000)
for topic, dataset in datasets.items():
run.add_dataset(topic, dataset)
print(run.nominal_url)
The run spans every uploaded topic, from the earliest sample to the latest. Open the run in Nominal to chart topics side by side in a workbook.
ulog_info -v <log>.ulg, also from pyulog, prints the log’s start time, duration and flight computer details.