|
1 | 1 | ''' |
2 | 2 | # @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/) |
4 | 4 | # Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology |
5 | 5 | # @detail: |
6 | 6 | # 1. Play the data you want in open3d, and save the view control to json file. |
|
13 | 13 | # 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. |
14 | 14 | # 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) |
15 | 15 | # 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. |
16 | 17 | ''' |
17 | 18 |
|
18 | 19 | import open3d as o3d |
19 | 20 | import os, time |
20 | 21 | from typing import List, Callable |
21 | 22 | from functools import partial |
| 23 | +import numpy as np |
22 | 24 |
|
23 | 25 | def hex_to_rgb(hex_color): |
24 | 26 | hex_color = hex_color.lstrip("#") |
@@ -48,12 +50,12 @@ def __init__(self, view_file=None, window_title="Default", save_folder="logs/img |
48 | 50 | "\t[SPACE] to pause/start\n" |
49 | 51 | "\t[ESC/Q] to exit\n" |
50 | 52 | "\t [P] to save screen and viewpoint\n" |
51 | | - "\t [N] to step\n" |
| 53 | + "\t [D] to step next\n" |
52 | 54 | ) |
53 | 55 | self._register_key_callback(["Ā", "Q", "\x1b"], self._quit) |
54 | 56 | self._register_key_callback(["P"], self._save_screen) |
55 | 57 | 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) |
57 | 59 |
|
58 | 60 | def show(self, assets: List): |
59 | 61 | self.vis.clear_geometries() |
@@ -113,6 +115,106 @@ def _save_screen(self, vis): |
113 | 115 | vis.capture_screen_image(png_file) |
114 | 116 | print(f"ScreenShot saved to: {png_file}, Please check it.") |
115 | 117 |
|
| 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 | + |
116 | 218 | if __name__ == "__main__": |
117 | 219 | json_content = """{ |
118 | 220 | "class_name" : "ViewTrajectory", |
|
0 commit comments