Skip to content

Commit 67a6d47

Browse files
Add rerun and open3d multi visualizer (#3)
* feat: add o3d multi visualizer, allows parsing fwd and back * feat: add rerun visualizer * style(vis): open3d vis and add mode for people easily change in arg --mode. * add example command for demo data * fix(rerun): align rerun to fire also and enable mutiple input. I agree it's too heavy since log times pc but only color difference. It looks like rerun rather than our problem. * chore(vis): limit rerun frame id to avoid long time vis. * update eval_metric to np.nan * add rerun-sdk to sftool env. * fix(o3d): align single MyVisualizer with Multi the same key. --------- Co-authored-by: Kin <kinzhangglimmer@gmail.com>
1 parent efe9581 commit 67a6d47

9 files changed

Lines changed: 353 additions & 14 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,14 @@ python tools/visualization.py --res_name 'seflow_best' --data_dir /home/kin/data
204204

205205
https://github.com/user-attachments/assets/f031d1a2-2d2f-4947-a01f-834ed1c146e6
206206

207+
Or another way to interact with [rerun](https://github.com/rerun-io/rerun) but please only vis scene by scene, not all at once.
208+
209+
```bash
210+
python tools/visualization_rerun.py --data_dir /home/kin/data/av2/h5py/demo/train --res_name "['flow', 'deflow']"
211+
```
212+
213+
https://github.com/user-attachments/assets/07e8d430-a867-42b7-900a-11755949de21
214+
207215

208216
## Acknowledgement
209217

dataprocess/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ If you want to use all datasets above, there is a specific process environment i
1616

1717
```bash
1818
conda env create -f envprocess.yaml
19-
conda activate dataprocess
19+
conda activate sftool
2020
# NOTE we need **manually reinstall numpy** (higher than 1.22)
2121
# * since waymo package force numpy==1.21.5, BUT!
2222
# * hdbscan w. numpy<1.22.0 will raise error: 'numpy.float64' object cannot be interpreted as an integer

envprocess.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: dataprocess
1+
name: sftool
22
channels:
33
- conda-forge
44
- pytorch
@@ -16,6 +16,7 @@ dependencies:
1616
- fire
1717
- hdbscan
1818
- s5cmd
19+
- rerun-sdk
1920
- pip:
2021
- nuscenes-devkit
2122
- av2==0.2.1

src/utils/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,15 @@ class bc:
77
FAIL = '\033[91m'
88
ENDC = '\033[0m'
99
BOLD = '\033[1m'
10-
UNDERLINE = '\033[4m'
10+
UNDERLINE = '\033[4m'
11+
12+
13+
def hex_to_rgb(hex_color):
14+
hex_color = hex_color.lstrip("#")
15+
return tuple(int(hex_color[i:i + 2], 16) / 255.0 for i in (0, 2, 4))
16+
17+
color_map_hex = ['#a6cee3', '#de2d26', '#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00',\
18+
'#cab2d6','#6a3d9a','#ffff99','#b15928', '#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3',\
19+
'#fdb462','#b3de69','#fccde5','#d9d9d9','#bc80bd','#ccebc5','#ffed6f']
20+
21+
color_map = [hex_to_rgb(color) for color in color_map_hex]

src/utils/eval_metric.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ def __init__(self, class_names: List[str], speed_buckets: List[Tuple[float, floa
106106
), f"speed_buckets must have at least one entry, got {len(self.speed_buckets)}"
107107

108108
# By default, NaNs are not counted in np.nanmean
109-
self.epe_storage_matrix = np.zeros((len(class_names), len(self.speed_buckets))) * np.NaN
110-
self.speed_storage_matrix = np.zeros((len(class_names), len(self.speed_buckets))) * np.NaN
109+
self.epe_storage_matrix = np.zeros((len(class_names), len(self.speed_buckets))) * np.nan
110+
self.speed_storage_matrix = np.zeros((len(class_names), len(self.speed_buckets))) * np.nan
111111
self.count_storage_matrix = np.zeros(
112112
(len(class_names), len(self.speed_buckets)), dtype=np.int64
113113
)

src/utils/mics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,13 @@ def move_hue_on_S_axis(hues, factors):
234234

235235

236236
class HDF5Data:
237-
def __init__(self, directory, flow_view=False, vis_name="flow"):
237+
def __init__(self, directory, flow_view=False, vis_name=["flow"]):
238238
'''
239239
directory: the directory of the dataset
240240
t_x: how many past frames we want to extract
241241
'''
242242
self.flow_view = flow_view
243-
self.vis_name = vis_name
243+
self.vis_name = vis_name if isinstance(vis_name, list) else [vis_name]
244244
self.directory = directory
245245
with open(os.path.join(self.directory, 'index_total.pkl'), 'rb') as f:
246246
self.data_index = pickle.load(f)
@@ -285,7 +285,7 @@ def __getitem__(self, index):
285285
data_dict['pc0'] = f[key]['lidar'][:]
286286
data_dict['gm0'] = f[key]['ground_mask'][:]
287287
data_dict['pose0'] = f[key]['pose'][:]
288-
for flow_key in [self.vis_name, 'dufo_label', 'label']:
288+
for flow_key in self.vis_name + ['dufo_label', 'label']:
289289
if flow_key in f[key]:
290290
data_dict[flow_key] = f[key][flow_key][:]
291291

src/utils/o3d_view.py

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'''
22
# @date: 2023-1-26 16:38
3-
# @author: Qingwen Zhang (https://kin-zhang.github.io/)
3+
# @author: Qingwen Zhang (https://kin-zhang.github.io/), Ajinkya Khoche (https://ajinkyakhoche.github.io/)
44
# Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology
55
# @detail:
66
# 1. Play the data you want in open3d, and save the view control to json file.
@@ -13,12 +13,14 @@
1313
# 2024-08-23 21:41(Qingwen): remove totally on view setting from scratch but use open3d>=0.18.0 version for set_view from json text func.
1414
# 2024-04-15 12:06(Qingwen): show a example json text. add hex_to_rgb, color_map_hex, color_map (for color points if needed)
1515
# 2024-01-27 0:41(Qingwen): update MyVisualizer class, reference from kiss-icp: https://github.com/PRBonn/kiss-icp/blob/main/python/kiss_icp/tools/visualizer.py
16+
# 2024-09-10 (Ajinkya): Add MyMultiVisualizer class to view multiple windows at once, allow forward and backward playback, create bev square for giving a sense of metric scale.
1617
'''
1718

1819
import open3d as o3d
1920
import os, time
2021
from typing import List, Callable
2122
from functools import partial
23+
import numpy as np
2224

2325
def hex_to_rgb(hex_color):
2426
hex_color = hex_color.lstrip("#")
@@ -48,12 +50,12 @@ def __init__(self, view_file=None, window_title="Default", save_folder="logs/img
4850
"\t[SPACE] to pause/start\n"
4951
"\t[ESC/Q] to exit\n"
5052
"\t [P] to save screen and viewpoint\n"
51-
"\t [N] to step\n"
53+
"\t [D] to step next\n"
5254
)
5355
self._register_key_callback(["Ā", "Q", "\x1b"], self._quit)
5456
self._register_key_callback(["P"], self._save_screen)
5557
self._register_key_callback([" "], self._start_stop)
56-
self._register_key_callback(["N"], self._next_frame)
58+
self._register_key_callback(["D"], self._next_frame)
5759

5860
def show(self, assets: List):
5961
self.vis.clear_geometries()
@@ -113,6 +115,106 @@ def _save_screen(self, vis):
113115
vis.capture_screen_image(png_file)
114116
print(f"ScreenShot saved to: {png_file}, Please check it.")
115117

118+
119+
def create_bev_square(size=409.6, color=[68/255,114/255,196/255]):
120+
# Create the vertices of the square
121+
half_size = size / 2.0
122+
vertices = np.array([
123+
[-half_size, -half_size, 0],
124+
[half_size, -half_size, 0],
125+
[half_size, half_size, 0],
126+
[-half_size, half_size, 0]
127+
])
128+
129+
# Define the square as a LineSet for visualization
130+
lines = [[0, 1], [1, 2], [2, 3], [3, 0]]
131+
colors = [color for _ in lines]
132+
133+
line_set = o3d.geometry.LineSet(
134+
points=o3d.utility.Vector3dVector(vertices),
135+
lines=o3d.utility.Vector2iVector(lines)
136+
)
137+
line_set.colors = o3d.utility.Vector3dVector(colors)
138+
139+
return line_set
140+
141+
class MyMultiVisualizer(MyVisualizer):
142+
def __init__(self, view_file=None, flow_mode=['flow'], screen_width=2500, screen_height = 1375):
143+
self.params = None
144+
self.view_file = view_file
145+
self.block_vis = True
146+
self.play_crun = False
147+
self.reset_bounding_box = True
148+
self.playback_direction = 1 # 1:forward, -1:backward
149+
150+
self.vis = []
151+
# self.o3d_vctrl = []
152+
153+
# Define width and height for each window
154+
window_width = screen_width // 2
155+
window_height = screen_height // 2
156+
# Define positions for the four windows
157+
epsilon = 150
158+
positions = [
159+
(0, 0), # Top-left
160+
(screen_width - window_width + epsilon, 0), # Top-right
161+
(0, screen_height - window_height + epsilon), # Bottom-left
162+
(screen_width - window_width + epsilon, screen_height - window_height + epsilon) # Bottom-right
163+
]
164+
165+
for i, mode in enumerate(flow_mode):
166+
window_title = f"view {'ground truth flow' if mode == 'flow' else f'{mode} flow'}, `SPACE` start/stop"
167+
v = o3d.visualization.VisualizerWithKeyCallback()
168+
v.create_window(window_name=window_title, width=window_width, height=window_height, left=positions[i%len(positions)][0], top=positions[i%len(positions)][1])
169+
# self.o3d_vctrl.append(ViewControl(v.get_view_control(), view_file=view_file))
170+
self.vis.append(v)
171+
172+
self._register_key_callback(["Ā", "Q", "\x1b"], self._quit)
173+
self._register_key_callback([" "], self._start_stop)
174+
self._register_key_callback(["D"], self._next_frame)
175+
self._register_key_callback(["A"], self._prev_frame)
176+
print(
177+
f"\n{window_title.capitalize()} initialized. Press:\n"
178+
"\t[SPACE] to pause/start\n"
179+
"\t[ESC/Q] to exit\n"
180+
"\t [P] to save screen and viewpoint\n"
181+
"\t [D] to step next\n"
182+
"\t [A] to step previous\n"
183+
)
184+
185+
def update(self, assets_list: List, clear: bool = True):
186+
if clear:
187+
[v.clear_geometries() for v in self.vis]
188+
189+
for i, assets in enumerate(assets_list):
190+
[self.vis[i].add_geometry(asset, reset_bounding_box=False) for asset in assets]
191+
self.vis[i].update_geometry(assets[-1])
192+
193+
if self.reset_bounding_box:
194+
[v.reset_view_point(True) for v in self.vis]
195+
if self.view_file is not None:
196+
# [o.read_viewTfile(self.view_file) for o in self.o3d_vctrl]
197+
[v.set_view_status(open(self.view_file).read()) for v in self.vis]
198+
self.reset_bounding_box = False
199+
200+
[v.update_renderer() for v in self.vis]
201+
while self.block_vis:
202+
[v.poll_events() for v in self.vis]
203+
if self.play_crun:
204+
break
205+
self.block_vis = not self.block_vis
206+
207+
def _register_key_callback(self, keys: List, callback: Callable):
208+
for key in keys:
209+
[v.register_key_callback(ord(str(key)), partial(callback)) for v in self.vis]
210+
def _next_frame(self, vis):
211+
self.block_vis = not self.block_vis
212+
self.playback_direction = 1
213+
def _prev_frame(self, vis):
214+
self.block_vis = not self.block_vis
215+
self.playback_direction = -1
216+
217+
116218
if __name__ == "__main__":
117219
json_content = """{
118220
"class_name" : "ViewTrajectory",

tools/visualization.py

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
"""
22
# Created: 2023-11-29 21:22
33
# Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology
4-
# Author: Qingwen Zhang (https://kin-zhang.github.io/)
4+
# Author: Qingwen Zhang (https://kin-zhang.github.io/), Ajinkya Khoche (https://ajinkyakhoche.github.io/)
55
#
6-
# This file is part of DeFlow (https://github.com/KTH-RPL/DeFlow).
6+
# This file is part of OpenSceneFlow (https://github.com/KTH-RPL/OpenSceneFlow).
77
# If you find this repo helpful, please cite the respective publication as
88
# listed on the above website.
99
#
1010
# Description: view scene flow dataset after preprocess.
11+
12+
# CHANGELOG:
13+
# 2024-09-10 (Ajinkya): Add vis_multiple(), to visualize multiple flow modes at once.
14+
15+
# Usage: (flow is ground truth flow, `other_name` is the estimated flow from the model)
16+
* python tools/visualization.py --data_dir /home/kin/data/av2/h5py/demo/train --res_name 'flow' --mode vis
17+
* python tools/visualization.py --data_dir /home/kin/data/av2/h5py/demo/train --res_name "['flow', 'deflow' , 'ssf']" --mode mul
18+
1119
"""
1220

1321
import numpy as np
@@ -19,7 +27,7 @@
1927
BASE_DIR = os.path.abspath(os.path.join( os.path.dirname( __file__ ), '..' ))
2028
sys.path.append(BASE_DIR)
2129
from src.utils.mics import HDF5Data, flow_to_rgb
22-
from src.utils.o3d_view import MyVisualizer, color_map
30+
from src.utils.o3d_view import MyVisualizer, MyMultiVisualizer, color_map, create_bev_square
2331

2432

2533
VIEW_FILE = f"{BASE_DIR}/assets/view/av2.json"
@@ -64,7 +72,10 @@ def vis(
6472
res_name: str = "flow", # "flow", "flow_est"
6573
start_id: int = 0,
6674
point_size: float = 2.0,
75+
mode: str = "vis",
6776
):
77+
if mode != "vis":
78+
return
6879
dataset = HDF5Data(data_dir, vis_name=res_name, flow_view=True)
6980
o3d_vis = MyVisualizer(view_file=VIEW_FILE, window_title=f"view {'ground truth flow' if res_name == 'flow' else f'{res_name} flow'}, `SPACE` start/stop")
7081

@@ -111,8 +122,83 @@ def vis(
111122
pcd.colors = o3d.utility.Vector3dVector(flow_color)
112123
o3d_vis.update([pcd, o3d.geometry.TriangleMesh.create_coordinate_frame(size=2)])
113124

125+
126+
def vis_multiple(
127+
data_dir: str ="/home/kin/data/av2/preprocess/sensor/mini",
128+
res_name: list = ["flow"],
129+
start_id: int = 0,
130+
point_size: float = 3.0,
131+
tone: str = 'dark',
132+
mode: str = "mul",
133+
):
134+
if mode != "mul":
135+
return
136+
assert isinstance(res_name, list), "vis_multiple() needs a list as flow_mode"
137+
dataset = HDF5Data(data_dir, vis_name=res_name, flow_view=True)
138+
o3d_vis = MyMultiVisualizer(view_file=VIEW_FILE, flow_mode=res_name)
139+
140+
for v in o3d_vis.vis:
141+
opt = v.get_render_option()
142+
if tone == 'bright':
143+
background_color = np.asarray([216, 216, 216]) / 255.0 # offwhite
144+
# background_color = np.asarray([1, 1, 1])
145+
pcd_color = [0.25, 0.25, 0.25]
146+
elif tone == 'dark':
147+
background_color = np.asarray([80/255, 90/255, 110/255]) # dark
148+
pcd_color = [1., 1., 1.]
149+
150+
opt.background_color = background_color
151+
opt.point_size = point_size
152+
153+
data_id = start_id
154+
pbar = tqdm(range(0, len(dataset)))
155+
156+
while data_id >= 0 and data_id < len(dataset):
157+
data = dataset[data_id]
158+
now_scene_id = data['scene_id']
159+
pbar.set_description(f"id: {data_id}, scene_id: {now_scene_id}, timestamp: {data['timestamp']}")
160+
161+
pc0 = data['pc0']
162+
gm0 = data['gm0']
163+
pose0 = data['pose0']
164+
pose1 = data['pose1']
165+
ego_pose = np.linalg.inv(pose1) @ pose0
166+
167+
pose_flow = pc0[:, :3] @ ego_pose[:3, :3].T + ego_pose[:3, 3] - pc0[:, :3]
168+
169+
pcd_list = []
170+
for mode in res_name:
171+
pcd = o3d.geometry.PointCloud()
172+
if mode in ['dufo_label', 'label']:
173+
labels = data[mode]
174+
pcd_i = o3d.geometry.PointCloud()
175+
for label_i in np.unique(labels):
176+
pcd_i.points = o3d.utility.Vector3dVector(pc0[labels == label_i][:, :3])
177+
if label_i <= 0:
178+
pcd_i.paint_uniform_color([1.0, 1.0, 1.0])
179+
else:
180+
pcd_i.paint_uniform_color(color_map[label_i % len(color_map)])
181+
pcd += pcd_i
182+
elif mode in data:
183+
pcd.points = o3d.utility.Vector3dVector(pc0[:, :3])
184+
flow = data[mode] - pose_flow # ego motion compensation here.
185+
flow_color = flow_to_rgb(flow) / 255.0
186+
is_dynamic = np.linalg.norm(flow, axis=1) > 0.1
187+
flow_color[~is_dynamic] = pcd_color
188+
flow_color[gm0] = pcd_color
189+
pcd.colors = o3d.utility.Vector3dVector(flow_color)
190+
pcd_list.append([pcd, create_bev_square(),
191+
create_bev_square(size=204.8, color=[195/255,86/255,89/255]),
192+
o3d.geometry.TriangleMesh.create_coordinate_frame(size=2)])
193+
o3d_vis.update(pcd_list)
194+
195+
data_id += o3d_vis.playback_direction
196+
pbar.update(o3d_vis.playback_direction)
197+
198+
114199
if __name__ == '__main__':
115200
start_time = time.time()
116201
# fire.Fire(check_flow)
117202
fire.Fire(vis)
203+
fire.Fire(vis_multiple)
118204
print(f"Time used: {time.time() - start_time:.2f} s")

0 commit comments

Comments
 (0)