-
Notifications
You must be signed in to change notification settings - Fork 378
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<!--[metadata] | ||
title = "Drone LiDAR" | ||
tags = ["3d", "drone", "lidar"] | ||
description = "Display drone-based LiDAR data" | ||
--> | ||
|
||
|
||
Display drone LiDAR data kindly provided by [Flyability](https://www.flyability.com). | ||
|
||
|
||
## Running | ||
|
||
Install the example pacakge | ||
```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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
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 | ||
import rerun as rr | ||
from tqdm import tqdm | ||
|
||
DATA_DIR = Path(__file__).parent / "dataset" | ||
MAP_DATA_DIR = DATA_DIR / "map_data" | ||
if not DATA_DIR.exists(): | ||
DATA_DIR.mkdir() | ||
|
||
LIDAR_DATA_FILE = DATA_DIR / "livemap.las" | ||
TRAJECTORY_DATA_FILE = DATA_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: | ||
with 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.TimeSecondsColumn("time", non_repeating_times)], | ||
[rr.components.Position3DBatch(positions).partition(partitions)], | ||
) | ||
|
||
rr.log_components( | ||
"/lidar", | ||
[ | ||
# TODO(#6889): indicator component no longer needed not needed when we have tagged components | ||
rr.Points3D.indicator(), | ||
rr.components.Radius(-0.2), # negative radii are interpreted in UI units (instead of scene units) | ||
rr.components.Color((0, 0, 128)), | ||
], | ||
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.TimeSecondsColumn("time", timestamp)], | ||
[rr.components.Position3DBatch(positions)], | ||
) | ||
|
||
rr.log_components( | ||
"/drone", | ||
[ | ||
# TODO(#6889): indicator component no longer needed not needed when we have tagged components | ||
rr.Points3D.indicator(), | ||
rr.components.Radius(0.5), | ||
rr.components.Color([255, 0, 0]), | ||
], | ||
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.Horizontal(rrb.Spatial3DView(origin="/"), rrb.TimeSeriesView(origin="/aircraft")) | ||
# rr.script_setup(args, "rerun_example_air_traffic_data", default_blueprint=blueprint) | ||
|
||
rr.script_setup(args, "rerun_example_drone_lidar") | ||
|
||
log_lidar_data() | ||
log_drone_trajectory() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[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" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters