chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:05:14 +08:00
commit 2a547be7fe
7904 changed files with 1000926 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/dataset
+40
View File
@@ -0,0 +1,40 @@
<!--[metadata]
title = "Drone LiDAR"
tags = ["3D", "drone", "Lidar"]
description = "Display drone-based LiDAR data"
-->
<picture>
<img src="https://static.rerun.io/drone_lidar/95c49d78abc01513d344c06e2d9a0c8b84376a0d/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/drone_lidar/95c49d78abc01513d344c06e2d9a0c8b84376a0d/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/drone_lidar/95c49d78abc01513d344c06e2d9a0c8b84376a0d/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/drone_lidar/95c49d78abc01513d344c06e2d9a0c8b84376a0d/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/drone_lidar/95c49d78abc01513d344c06e2d9a0c8b84376a0d/1200w.png">
</picture>
## Background
This example displays drone-based indoor LiDAR data loaded from a [`.las`](https://en.wikipedia.org/wiki/LAS_file_format) file. This dataset contains 18.7M points, acquired at 4013 distinct time points (~4650 points per time point). The point data is loaded using the [laspy](https://laspy.readthedocs.io/en/latest/) Python package, and then sent in one go to the viewer thanks to the [`rr.send_columns()`](https://ref.rerun.io/docs/python/0.18.2/common/columnar_api/#rerun.send_columns) API and its `.partition()` helper. Together, these APIs enable associating subgroups of points with each of their corresponding, non-repeating timestamps.
[Flyability](https://www.flyability.com) kindly provided the data for this example.
## Running
Install the example package:
```bash
pip install -e examples/python/drone_lidar
```
To experiment with the provided example, simply execute the main Python script:
```bash
python -m drone_lidar
```
If you wish to customize it, explore additional features, or save it, use the CLI with the `--help` option for guidance:
```bash
python -m drone_lidar --help
```
+180
View File
@@ -0,0 +1,180 @@
from __future__ import annotations
import io
import typing
import zipfile
from argparse import ArgumentParser
from pathlib import Path
import laspy
import numpy as np
import numpy.typing as npt
import requests
from tqdm import tqdm
import rerun as rr
import rerun.blueprint as rrb
DATASET_DIR = Path(__file__).parent / "dataset"
if not DATASET_DIR.exists():
DATASET_DIR.mkdir()
LIDAR_DATA_FILE = DATASET_DIR / "livemap.las"
TRAJECTORY_DATA_FILE = DATASET_DIR / "livetraj.csv"
LIDAR_DATA_URL = "https://storage.googleapis.com/rerun-example-datasets/flyability/basement/livemap.las.zip"
TRAJECTORY_DATA_URL = "https://storage.googleapis.com/rerun-example-datasets/flyability/basement/livetraj.csv"
def download_with_progress(url: str, what: str) -> io.BytesIO:
"""Download a file with a 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 unzip_file_from_archive_with_progress(zip_data: typing.BinaryIO, file_name: str, dest_dir: Path) -> None:
"""Unzip the file named `file_name` from the zip archive contained in `zip_data` to `dest_dir`."""
with zipfile.ZipFile(zip_data, "r") as zip_ref:
file_info = zip_ref.getinfo(file_name)
total_size = file_info.file_size
with (
tqdm(
total=total_size,
desc=f"Extracting file {file_name}",
unit="iB",
unit_scale=True,
unit_divisor=1024,
) as progress,
zip_ref.open(file_name) as source,
open(dest_dir / file_name, "wb") as target,
):
for chunk in iter(lambda: source.read(1024 * 1024), b""):
target.write(chunk)
progress.update(len(chunk))
def download_dataset() -> None:
if not LIDAR_DATA_FILE.exists():
unzip_file_from_archive_with_progress(
download_with_progress(LIDAR_DATA_URL, LIDAR_DATA_FILE.name),
LIDAR_DATA_FILE.name,
LIDAR_DATA_FILE.parent,
)
if not TRAJECTORY_DATA_FILE.exists():
TRAJECTORY_DATA_FILE.write_bytes(
download_with_progress(TRAJECTORY_DATA_URL, TRAJECTORY_DATA_FILE.name).getvalue(),
)
# TODO(#7333): this utility should be included in the Rerun SDK
def compute_partitions(
times: npt.NDArray[np.float64],
) -> tuple[typing.Sequence[float], typing.Sequence[np.uintp]]:
"""
Compute partitions given possibly repeating times.
This function returns two arrays:
- Non-repeating times: a filtered version of `times` where repeated times are removed.
- Partitions: an array of integers where each element indicates the number of elements for the corresponding time
values in the original `times` array.
By construction, both arrays should have the same length, and the sum of all elements in `partitions` should be
equal to the length of `times`.
"""
change_indices = (np.argwhere(times != np.concatenate([times[1:], np.array([np.nan])])).T + 1).reshape(-1)
partitions = np.concatenate([[change_indices[0]], np.diff(change_indices)])
non_repeating_times = times[change_indices - 1]
assert np.sum(partitions) == len(times)
assert len(non_repeating_times) == len(partitions)
return non_repeating_times, partitions # type: ignore[return-value]
def log_lidar_data() -> None:
las_data = laspy.read(LIDAR_DATA_FILE)
# get positions and convert to meters
points = las_data.points
positions = np.column_stack((points.X / 1000.0, points.Y / 1000.0, points.Z / 1000.0))
times = las_data.gps_time
non_repeating_times, partitions = compute_partitions(times)
# log all positions at once using the computed partitions
rr.send_columns(
"/lidar",
[rr.TimeColumn("time", duration=non_repeating_times)],
rr.Points3D.columns(positions=positions).partition(partitions),
)
rr.log(
"/lidar",
# negative radii are interpreted in UI units (instead of scene units)
rr.Points3D.from_fields(colors=(128, 128, 255), radii=-0.1),
static=True,
)
def log_drone_trajectory() -> None:
data = np.genfromtxt(TRAJECTORY_DATA_FILE, delimiter=" ", skip_header=1)
timestamp = data[:, 0]
positions = data[:, 1:4]
rr.send_columns(
"/drone",
[rr.TimeColumn("time", duration=timestamp)],
rr.Points3D.columns(positions=positions),
)
rr.log(
"/drone",
rr.Points3D.from_fields(colors=(255, 0, 0), radii=0.5),
static=True,
)
def main() -> None:
parser = ArgumentParser(description="Visualize drone-based LiDAR data")
rr.script_add_args(parser)
args = parser.parse_args()
download_dataset()
blueprint = rrb.Spatial3DView(
origin="/",
time_ranges=[
rrb.VisibleTimeRange(
timeline="time",
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-60.0),
end=rrb.TimeRangeBoundary.cursor_relative(),
),
],
)
rr.script_setup(args, "rerun_example_drone_lidar", default_blueprint=blueprint)
log_lidar_data()
log_drone_trajectory()
if __name__ == "__main__":
main()
@@ -0,0 +1,13 @@
[project]
name = "drone_lidar"
version = "0.1.0"
readme = "README.md"
dependencies = ["laspy", "numpy", "requests", "rerun-sdk", "tqdm"]
[project.scripts]
drone_lidar = "drone_lidar:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"