Skip to content

Commit 20cec41

Browse files
authored
Add IMU example to manifest (#9319)
### Related Closes #9235 ### What Adds the IMU example to the nightly manifest - [x] Based on #9321 - [x] Upload the TUM VI dataset to a GCP bucket to make it available more reliably
1 parent 8e42d5f commit 20cec41

File tree

6 files changed

+145
-108
lines changed

6 files changed

+145
-108
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
dataset-corridor4_512_16*
1+
dataset/*
22

examples/python/imu_signals/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
title = "IMU signals"
33
tags = ["Plots"]
44
description = "Log multi dimensional signals under a single entity."
5+
thumbnail = "https://static.rerun.io/imu_signals/64f773d238a0456a0f233abeea7e521cfb871b67/480w.jpg"
56
thumbnail_dimensions = [480, 480]
7+
channel = "main"
8+
build_args = ["--seconds=10"]
69
-->
710

811
This example demonstrates how to log multi dimensional signals with the Rerun SDK, using the [TUM VI Benchmark](https://cvg.cit.tum.de/data/datasets/visual-inertial-dataset).
@@ -55,3 +58,11 @@ To experiment with the provided example, simply execute the main Python script:
5558
```bash
5659
python -m imu_signals
5760
```
61+
62+
## Attribution
63+
64+
This example uses a scene from the **TUM VI Benchmark dataset**, originally provided by [Technical University of Munich (TUM)](https://cvg.cit.tum.de/data/datasets/visual-inertial-dataset).
65+
The dataset is licensed under **Creative Commons Attribution 4.0 (CC BY 4.0)**.
66+
67+
- Original dataset: [TUM VI Benchmark](https://cvg.cit.tum.de/data/datasets/visual-inertial-dataset)
68+
- License details: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)

examples/python/imu_signals/imu_signals.py

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,119 @@
11
from __future__ import annotations
22

3+
import argparse
34
import os
4-
import pathlib
55
import tarfile
6+
from pathlib import Path
67

8+
import numpy as np
79
import pandas as pd
810
import requests
911
import rerun as rr
1012
from rerun import blueprint as rrb
13+
from tqdm.auto import tqdm
1114

12-
cwd = pathlib.Path(__file__).parent.resolve()
15+
DATA_DIR = Path(__file__).parent / "dataset"
1316

14-
DATASET_URL = "https://vision.in.tum.de/tumvi/exported/euroc/512_16/dataset-corridor4_512_16.tar"
17+
DATASET_URL = "https://storage.googleapis.com/rerun-example-datasets/imu_signals/tum_vi_corridor4_512_16.tar"
1518
DATASET_NAME = "dataset-corridor4_512_16"
1619
XYZ_AXIS_NAMES = ["x", "y", "z"]
1720
XYZ_AXIS_COLORS = [[(231, 76, 60), (39, 174, 96), (52, 120, 219)]]
1821

1922

2023
def main() -> None:
21-
dataset_path = cwd / DATASET_NAME
24+
dataset_path = DATA_DIR / DATASET_NAME
2225
if not dataset_path.exists():
23-
_download_dataset(cwd)
26+
_download_dataset(DATA_DIR)
27+
28+
parser = argparse.ArgumentParser(description="Visualizes the TUM Visual-Inertial dataset using the Rerun SDK.")
29+
parser.add_argument(
30+
"--seconds",
31+
type=float,
32+
default=float("inf"),
33+
help="If specified, limits the number of seconds logged",
34+
)
35+
rr.script_add_args(parser)
36+
args = parser.parse_args()
37+
38+
blueprint = rrb.Horizontal(
39+
rrb.Vertical(
40+
rrb.TimeSeriesView(
41+
origin="gyroscope",
42+
name="Gyroscope",
43+
overrides={
44+
# TODO(#9022): Pluralize series line type.
45+
"/gyroscope": rr.SeriesLine.from_fields(name=XYZ_AXIS_NAMES, color=XYZ_AXIS_COLORS), # type: ignore[arg-type]
46+
},
47+
),
48+
rrb.TimeSeriesView(
49+
origin="accelerometer",
50+
name="Accelerometer",
51+
overrides={
52+
# TODO(#9022): Pluralize series line type.
53+
"/accelerometer": rr.SeriesLine.from_fields(name=XYZ_AXIS_NAMES, color=XYZ_AXIS_COLORS), # type: ignore[arg-type]
54+
},
55+
),
56+
),
57+
rrb.Spatial3DView(origin="/", name="World position"),
58+
column_shares=[0.45, 0.55],
59+
)
60+
61+
rr.script_setup(args, "rerun_example_imu_signals", default_blueprint=blueprint)
2462

25-
_setup_rerun()
26-
_log_imu_data()
27-
_log_image_data()
28-
_log_gt_imu()
63+
_log_imu_data(args.seconds)
64+
_log_image_data(args.seconds)
65+
_log_gt_imu(args.seconds)
2966

3067

31-
def _download_dataset(root: pathlib.Path, dataset_url: str = DATASET_URL) -> None:
68+
def _download_dataset(root: Path, dataset_url: str = DATASET_URL) -> None:
3269
os.makedirs(root, exist_ok=True)
33-
tar_path = os.path.join(root, "dataset-corridor4_512_16.tar")
34-
print("Downloading dataset...")
35-
with requests.get(dataset_url, stream=True) as r:
36-
r.raise_for_status()
37-
with open(tar_path, "wb") as f:
38-
for chunk in r.iter_content(chunk_size=8192):
39-
if chunk:
40-
f.write(chunk)
70+
tar_path = os.path.join(root, f"{DATASET_NAME}.tar")
71+
response = requests.get(dataset_url, stream=True)
72+
73+
total_size = int(response.headers.get("content-length", 0))
74+
block_size = 1024
75+
76+
with tqdm(desc="Downloading dataset", total=total_size, unit="B", unit_scale=True) as pb:
77+
with open(tar_path, "wb") as file:
78+
for data in response.iter_content(chunk_size=block_size):
79+
pb.update(len(data))
80+
file.write(data)
81+
82+
if total_size != 0 and pb.n != total_size:
83+
raise RuntimeError("Failed to download complete dataset!")
84+
4185
print("Extracting dataset...")
4286
with tarfile.open(tar_path, "r:") as tar:
4387
tar.extractall(path=root)
4488
os.remove(tar_path)
4589

4690

47-
def _setup_rerun() -> None:
48-
rr.init("rerun_example_imu_data", spawn=True)
49-
50-
rr.send_blueprint(
51-
rrb.Horizontal(
52-
rrb.Vertical(
53-
rrb.TimeSeriesView(
54-
origin="gyroscope",
55-
name="Gyroscope",
56-
overrides={
57-
# TODO(#9022): Pluralize series line type.
58-
"/gyroscope": rr.SeriesLine.from_fields(name=XYZ_AXIS_NAMES, color=XYZ_AXIS_COLORS), # type: ignore[arg-type]
59-
},
60-
),
61-
rrb.TimeSeriesView(
62-
origin="accelerometer",
63-
name="Accelerometer",
64-
overrides={
65-
# TODO(#9022): Pluralize series line type.
66-
"/accelerometer": rr.SeriesLine.from_fields(name=XYZ_AXIS_NAMES, color=XYZ_AXIS_COLORS), # type: ignore[arg-type]
67-
},
68-
),
69-
),
70-
rrb.Spatial3DView(origin="/", name="World position"),
71-
column_shares=[0.45, 0.55],
72-
),
73-
)
74-
75-
76-
def _log_imu_data() -> None:
91+
def _log_imu_data(max_time_sec: float) -> None:
7792
imu_data = pd.read_csv(
78-
cwd / DATASET_NAME / "dso/imu.txt",
93+
DATA_DIR / DATASET_NAME / "dso/imu.txt",
7994
sep=" ",
8095
header=0,
8196
names=["timestamp", "gyro.x", "gyro.y", "gyro.z", "accel.x", "accel.y", "accel.z"],
8297
comment="#",
8398
)
8499

85-
times = rr.TimeColumn("timestamp", timestamp=imu_data["timestamp"])
100+
timestamps = imu_data["timestamp"].to_numpy()
101+
max_time_ns = imu_data["timestamp"][0] + max_time_sec * 1e9
102+
selected = imu_data[imu_data["timestamp"] <= max_time_ns]
103+
104+
timestamps = selected["timestamp"].astype("datetime64[ns]")
105+
times = rr.TimeColumn("timestamp", timestamp=timestamps)
86106

87-
gyro = imu_data[["gyro.x", "gyro.y", "gyro.z"]]
107+
gyro = selected[["gyro.x", "gyro.y", "gyro.z"]].to_numpy()
88108
rr.send_columns("/gyroscope", indexes=[times], columns=rr.Scalar.columns(scalar=gyro))
89109

90-
accel = imu_data[["accel.x", "accel.y", "accel.z"]]
110+
accel = selected[["accel.x", "accel.y", "accel.z"]]
91111
rr.send_columns("/accelerometer", indexes=[times], columns=rr.Scalar.columns(scalar=accel))
92112

93113

94-
def _log_image_data() -> None:
114+
def _log_image_data(max_time_sec: float) -> None:
95115
times = pd.read_csv(
96-
cwd / DATASET_NAME / "dso/cam0/times.txt",
116+
DATA_DIR / DATASET_NAME / "dso/cam0/times.txt",
97117
sep=" ",
98118
header=0,
99119
names=["filename", "timestamp", "exposure_time"],
@@ -103,35 +123,48 @@ def _log_image_data() -> None:
103123

104124
rr.set_time("timestamp", timestamp=times["timestamp"][0])
105125
rr.log(
106-
"/cam0",
126+
"/world",
127+
rr.Transform3D(rotation_axis_angle=rr.RotationAxisAngle(axis=(1, 0, 0), angle=-np.pi / 2)),
128+
static=True,
129+
)
130+
rr.log(
131+
"/world/cam0",
107132
rr.Pinhole(
108133
focal_length=(0.373 * 512, 0.373 * 512),
109134
resolution=(512, 512),
110-
camera_xyz=rr.components.ViewCoordinates.FLU,
111135
image_plane_distance=0.4,
112136
),
113137
static=True,
114138
)
115139

140+
max_time_sec = times["timestamp"][0] + max_time_sec
116141
for _, (filename, timestamp, _) in times.iterrows():
117-
image_path = cwd / DATASET_NAME / "dso/cam0/images" / f"{filename}.png"
142+
if timestamp > max_time_sec:
143+
break
144+
145+
image_path = DATA_DIR / DATASET_NAME / "dso/cam0/images" / f"{filename}.png"
118146
rr.set_time("timestamp", timestamp=timestamp)
119-
rr.log("/cam0/image", rr.ImageEncoded(path=image_path))
147+
rr.log("/world/cam0/image", rr.EncodedImage(path=image_path))
120148

121149

122-
def _log_gt_imu() -> None:
150+
def _log_gt_imu(max_time_sec: float) -> None:
123151
gt_imu = pd.read_csv(
124-
cwd / DATASET_NAME / "dso/gt_imu.csv",
152+
DATA_DIR / DATASET_NAME / "dso/gt_imu.csv",
125153
sep=",",
126154
header=0,
127155
names=["timestamp", "t.x", "t.y", "t.z", "q.w", "q.x", "q.y", "q.z"],
128156
comment="#",
129157
)
130158

131-
times = rr.TimeColumn("timestamp", timestamp=gt_imu["timestamp"])
159+
timestamps = gt_imu["timestamp"].to_numpy()
160+
max_time_ns = gt_imu["timestamp"][0] + max_time_sec * 1e9
161+
selected = gt_imu[gt_imu["timestamp"] <= max_time_ns]
132162

133-
translations = gt_imu[["t.x", "t.y", "t.z"]]
134-
quaternions = gt_imu[
163+
timestamps = selected["timestamp"].astype("datetime64[ns]")
164+
times = rr.TimeColumn("timestamp", timestamp=timestamps)
165+
166+
translations = selected[["t.x", "t.y", "t.z"]]
167+
quaternions = selected[
135168
[
136169
"q.x",
137170
"q.y",
@@ -140,9 +173,12 @@ def _log_gt_imu() -> None:
140173
]
141174
]
142175
rr.send_columns(
143-
"/cam0",
176+
"/world/cam0",
144177
indexes=[times],
145-
columns=rr.Transform3D.columns(translation=translations, quaternion=quaternions),
178+
columns=rr.Transform3D.columns(
179+
translation=translations,
180+
quaternion=quaternions,
181+
),
146182
)
147183

148184

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
[build-system]
2-
requires = ["hatchling"]
3-
build-backend = "hatchling.build"
4-
51
[project]
62
name = "imu_signals"
3+
description = "Log multi dimensional signals under a single entity."
74
version = "0.1.0"
85
requires-python = "<3.12"
9-
dependencies = ["numpy", "requests>=2.31,<3", "rerun-sdk", "pandas"]
10-
6+
dependencies = ["numpy", "requests>=2.31,<3", "rerun-sdk", "pandas", "tqdm"]
117

128
[project.scripts]
139
imu_signals = "imu_signals:main"
1410

11+
[build-system]
12+
requires = ["hatchling"]
13+
build-backend = "hatchling.build"
14+
15+
1516
[tool.rerun-example]
1617
skip = false

0 commit comments

Comments
 (0)