Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PX4 ulog example #9127

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions examples/python/px4_ulog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--[metadata]
title = "PX4 ulog"
tags = ["2D", "3D", "map", "drone"]
description = "Load and visualize a PX4 ulog drone log file"
-->


<!--
TODO:
thumbnail = "https://static.rerun.io/air_traffic_data/348dd2def3a55fd0bf481a35a0765eeacfa20b6f/480w.png"
thumbnail_dimensions = [480, 480]
#channel = "nightly"
-->

TODO: complete thse

Check warning on line 15 in examples/python/px4_ulog/README.md

View workflow job for this annotation

GitHub Actions / Checks / Spell Check

"thse" should be "the" or "these" or "this".

## Run the code

To run this example, make sure you have Python version at least 3.9, the Rerun repository checked out and the latest SDK installed:
```bash
pip install --upgrade rerun-sdk # install the latest Rerun SDK
git clone [email protected]:rerun-io/rerun.git # Clone the repository
cd rerun
git checkout latest # Check out the commit matching the latest SDK release
```
Install the necessary libraries specified in the requirements file:
```bash
pip install -e examples/python/air_traffic_data
```
To experiment with the provided example, simply execute the main Python script:
```bash
python -m air_traffic_data
```
If you wish to customize it, explore additional features, or save it use the CLI with the `--help` option for guidance:
```bash
python -m air_traffic_data --help
```
200 changes: 200 additions & 0 deletions examples/python/px4_ulog/px4_ulog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
from __future__ import annotations

import io
from argparse import ArgumentParser
from pathlib import Path

import numpy as np
import requests
import rerun as rr
import rerun.blueprint as rrb
from pyulog import ULog
from tqdm import tqdm

DATASET_DIR = Path(__file__).parent / "dataset"
if not DATASET_DIR.exists():
DATASET_DIR.mkdir()

ULOG_FILE_PATH = DATASET_DIR / "log_file.ulg"

ULOG_FILE_URL = "https://github.com/foxglove/ulog/raw/refs/heads/main/tests/log_6_2021-7-20-11-41-56.ulg"


def download_with_progress(url: str, what: str) -> io.BytesIO:
"""Download file with tqdm progress bar."""
chunk_size = 1024 * 1024
resp = requests.get(url, stream=True)
total_size = int(resp.headers.get("content-length", 0))
with tqdm(
desc=f"Downloading {what}…",
total=total_size,
unit="iB",
unit_scale=True,
unit_divisor=1024,
) as progress:
download_file = io.BytesIO()
for data in resp.iter_content(chunk_size):
download_file.write(data)
progress.update(len(data))

download_file.seek(0)
return download_file


def log_initial_parameters(log_file: ULog) -> None:
param_table_content = "\n".join([f"| {param} | {value} |" for param, value in log_file.initial_parameters.items()])

param_text = f"""
## Initial Parameters

| Parameter | Value |
|-----------|-------|
{param_table_content}
"""

rr.log("initial_parameters", rr.TextDocument(text=param_text, media_type="text/markdown"), static=True)


def log_messages(log_file: ULog) -> None:
for msg in log_file.logged_messages:
rr.set_time_seconds("timestamp", msg.timestamp / 1e6)
rr.log("messages", rr.TextLog(text=msg.message, level=msg.log_level_str()))


def log_data(log_file: ULog) -> None:
for data in log_file.data_list:
name = data.name

columns = data.data.keys() - {"timestamp"}

time_columns = [rr.TimeSecondsColumn("timestamp", data.data["timestamp"] / 1e6)]

if "timestamp_sample" in columns:
time_columns.append(rr.TimeSecondsColumn("timestamp_sample", data.data["timestamp_sample"] / 1e6))
columns.remove("timestamp_sample")

all_columns = {}
for column in columns:
try:
col_name, idx = column.split("[")
idx = int(idx.strip("]"))
except ValueError:
col_name = column
idx = None

if idx is None:
all_columns[col_name] = data.data[column]
else:
all_columns.setdefault(col_name, {})[idx] = data.data[column]

for col_name, col_data in all_columns.items():
entity_path = f"{name}/{col_name.replace('.', '/')}"

if isinstance(col_data, dict):
arr = np.vstack([col_data[i] for i in sorted(col_data.keys())]).T
else:
arr = col_data

rr.send_columns(entity_path, indexes=time_columns, columns=rr.Scalar.columns(scalar=arr))

if name == "vehicle_global_position":
latlon = np.vstack([data.data["lat"], data.data["lon"]]).T
rr.send_columns(name, indexes=time_columns, columns=rr.GeoPoints.columns(positions=latlon))
elif name == "vehicle_local_position":
pos3d = np.vstack([data.data["x"], data.data["y"], -data.data["z"]]).T
pos2d = np.vstack([data.data["x"], data.data["y"]]).T
rr.send_columns(
f"{name}/pos",
indexes=time_columns,
columns=[
*rr.Points3D.columns(positions=pos3d),
*rr.Points2D.columns(positions=pos2d),
],
)

v2d = np.vstack([data.data["vx"], data.data["vy"]]).T
v3d = np.vstack([data.data["vx"], data.data["vy"], -data.data["vz"]]).T
rr.send_columns(
f"{name}/vel",
indexes=time_columns,
columns=[
*rr.Arrows3D.columns(origins=pos3d, vectors=v3d),
*rr.Arrows2D.columns(origins=pos2d, vectors=v2d),
],
)
elif name == "position_setpoint_triplet":
latlon = np.vstack([data.data["next.lat"], data.data["next.lon"]]).T
radius = data.data["next.loiter_radius"]

rr.send_columns(
f"{name}/next/latlon",
indexes=time_columns,
columns=rr.GeoPoints.columns(positions=latlon, radii=radius)
)


def main() -> None:
parser = ArgumentParser(description="Loada and visualize a PX4 ulog file")

parser.add_argument("--uuid", type=str, help="UUID of log file on `review.px4.io`")

rr.script_add_args(parser)
args = parser.parse_args()

rr.script_setup(args, "rerun_example_px4_log")

if args.uuid is not None:
# TODO: download
file_path = DATASET_DIR / f"{args.uuid}.ulg"
else:
file_path = ULOG_FILE_PATH
if not file_path.exists():
ulog_file_data = download_with_progress(ULOG_FILE_URL, "ulog file")
file_path.write_bytes(ulog_file_data.read())

log_file = ULog(str(file_path))
log_initial_parameters(log_file)
log_messages(log_file)
log_data(log_file)

blueprint = rrb.Vertical(
rrb.Horizontal(
rrb.Tabs(
rrb.Spatial2DView(
name="2D",
overrides={
"vehicle_local_position/pos": [
# TODO: broken because of #8557
rrb.VisibleTimeRange(
"timestamp",
start=rrb.TimeRangeBoundary.infinite(),
end=rrb.TimeRangeBoundary.infinite(),
)
],
},
),
rrb.Spatial3DView(
name="3D",
overrides={
"vehicle_local_position/pos": [
rrb.VisibleTimeRange(
"timestamp",
start=rrb.TimeRangeBoundary.infinite(),
end=rrb.TimeRangeBoundary.infinite(),
)
],
},
),
rrb.MapView(name="Map"),
),
rrb.TextLogView(),
column_shares=[3, 1],
),
rrb.Tabs(*[rrb.TimeSeriesView(origin=data.name) for data in log_file.data_list]),
)

rr.send_blueprint(blueprint, make_active=False, make_default=True)


if __name__ == "__main__":
main()
19 changes: 19 additions & 0 deletions examples/python/px4_ulog/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "px4_ulog"
version = "0.1.0"
readme = "README.md"
dependencies = [
"numpy",
"pyulog",
"requests",
"rerun-sdk",
"tqdm",
]

[project.scripts]
px4_ulog = "px4_ulog:main"


[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Loading
Loading