1+ '''
2+ # Created: 2023-1-26 16:38
3+ # Updated: 2024-04-15 12:06
4+ # Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology
5+ # Author: Qingwen ZHANG (https://kin-zhang.github.io/)
6+ #
7+ # code gits: https://gist.github.com/Kin-Zhang/77e8aa77a998f1a4f7495357843f24ef
8+ #
9+ # Description as follows:
10+
11+ This file is for open3d view control set from view_file, which should be json
12+ 1. use normal way to open any geometry and set view by mouse you want
13+ 2. `CTRL+C` it will copy the view detail at this moment.
14+ 3. `CTRL+V` to json file, you can create new one
15+ 4. give the json file path
16+
17+ Check this part: http://www.open3d.org/docs/release/tutorial/visualization/visualization.html#Store-view-point
18+
19+ Test if you want by run this script: by press 'V' on keyboard, will set from json
20+
21+ # CHANGELOG:
22+ # 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)
23+ # 2024-01-27 0:41(Qingwen): update MyVisualizer class, reference from kiss-icp
24+ [python/kiss-icp/tools/visualizer.py](https://github.com/PRBonn/kiss-icp/blob/main/python/kiss_icp/tools/visualizer.py)
25+ '''
26+
27+ import open3d as o3d
28+ import json
29+ import os , sys
30+ from typing import List , Callable
31+ from functools import partial
32+
33+ def hex_to_rgb (hex_color ):
34+ hex_color = hex_color .lstrip ("#" )
35+ return tuple (int (hex_color [i :i + 2 ], 16 ) / 255.0 for i in (0 , 2 , 4 ))
36+
37+ color_map_hex = ['#a6cee3' , '#de2d26' , '#1f78b4' ,'#b2df8a' ,'#33a02c' ,'#fb9a99' ,'#e31a1c' ,'#fdbf6f' ,'#ff7f00' ,'#cab2d6' ,'#6a3d9a' ,'#ffff99' ,'#b15928' ,\
38+ '#8dd3c7' ,'#ffffb3' ,'#bebada' ,'#fb8072' ,'#80b1d3' ,'#fdb462' ,'#b3de69' ,'#fccde5' ,'#d9d9d9' ,'#bc80bd' ,'#ccebc5' ,'#ffed6f' ]
39+ color_map = [hex_to_rgb (color ) for color in color_map_hex ]
40+
41+ class ViewControl :
42+ def __init__ (self , vctrl : o3d .visualization .ViewControl , view_file = None ):
43+ self .vctrl = vctrl
44+ self .params = None
45+ if view_file is not None :
46+ print (f"Init with view_file from: { view_file } " )
47+ self .parse_file (view_file )
48+ self .set_param ()
49+ else :
50+ print ("Init without view_file" )
51+
52+ def read_viewTfile (self , view_file ):
53+ if view_file is None :
54+ return
55+ self .parse_file (view_file )
56+ self .set_param ()
57+
58+ def save_viewTfile (self , view_file ):
59+ return
60+
61+ def parse_file (self , view_file ):
62+ if view_file is None :
63+ print (f"\033 [91mNo specific view file. Skip to setup viewpoint in open3d. \033 [0m" )
64+ return
65+ if (os .path .exists (view_file )):
66+ with open ((view_file )) as user_file :
67+ file_contents = user_file .read ()
68+ self .params = json .loads (file_contents )
69+ else :
70+ print (f"\033 [91mDidn't find the file, please check it again: { view_file } \033 [0m" )
71+ print (f"NOTE: If you still have this error, please give the absulote path for view_file" )
72+ sys .exit ()
73+
74+ def set_param (self ):
75+ self .vctrl .change_field_of_view (self .params ['trajectory' ][0 ]['field_of_view' ])
76+ self .vctrl .set_front (self .params ['trajectory' ][0 ]['front' ])
77+ self .vctrl .set_lookat (self .params ['trajectory' ][0 ]['lookat' ])
78+ self .vctrl .set_up (self .params ['trajectory' ][0 ]['up' ])
79+ self .vctrl .set_zoom (self .params ['trajectory' ][0 ]['zoom' ])
80+
81+ class MyVisualizer :
82+ def __init__ (self , view_file = None , window_title = "Default" ):
83+ self .params = None
84+ self .vis = o3d .visualization .VisualizerWithKeyCallback ()
85+ self .vis .create_window (window_name = window_title )
86+ self .o3d_vctrl = ViewControl (self .vis .get_view_control (), view_file = view_file )
87+ self .view_file = view_file
88+
89+ self .block_vis = True
90+ self .play_crun = False
91+ self .reset_bounding_box = True
92+ print (
93+ f"\n { window_title .capitalize ()} initialized. Press:\n "
94+ "\t [SPACE] to pause/start\n "
95+ "\t [ESC] to exit\n "
96+ "\t [N] to step\n "
97+ )
98+ self ._register_key_callback (["Ā" , "Q" , "\x1b " ], self ._quit )
99+ self ._register_key_callback ([" " ], self ._start_stop )
100+ self ._register_key_callback (["N" ], self ._next_frame )
101+
102+ def show (self , assets : List ):
103+ self .vis .clear_geometries ()
104+
105+ for asset in assets :
106+ self .vis .add_geometry (asset )
107+ self .o3d_vctrl .read_viewTfile (self .view_file )
108+
109+ self .vis .update_renderer ()
110+ self .vis .poll_events ()
111+ self .vis .run ()
112+ self .vis .destroy_window ()
113+
114+ def update (self , assets : List , clear : bool = True ):
115+ if clear :
116+ self .vis .clear_geometries ()
117+
118+ for asset in assets :
119+ self .vis .add_geometry (asset , reset_bounding_box = False )
120+ self .vis .update_geometry (asset )
121+
122+ if self .reset_bounding_box :
123+ self .vis .reset_view_point (True )
124+ if self .view_file is not None :
125+ self .o3d_vctrl .read_viewTfile (self .view_file )
126+ self .reset_bounding_box = False
127+
128+ self .vis .update_renderer ()
129+ while self .block_vis :
130+ self .vis .poll_events ()
131+ if self .play_crun :
132+ break
133+ self .block_vis = not self .block_vis
134+
135+ def _register_key_callback (self , keys : List , callback : Callable ):
136+ for key in keys :
137+ self .vis .register_key_callback (ord (str (key )), partial (callback ))
138+ def _next_frame (self , vis ):
139+ self .block_vis = not self .block_vis
140+ def _start_stop (self , vis ):
141+ self .play_crun = not self .play_crun
142+ def _quit (self , vis ):
143+ print ("Destroying Visualizer. Thanks for using ^v^." )
144+ vis .destroy_window ()
145+ os ._exit (0 )
146+
147+ if __name__ == "__main__" :
148+ json_content = """{
149+ "class_name" : "ViewTrajectory",
150+ "interval" : 29,
151+ "is_loop" : false,
152+ "trajectory" :
153+ [
154+ {
155+ "boundingbox_max" : [ 3.9660897254943848, 2.427476167678833, 2.55859375 ],
156+ "boundingbox_min" : [ 0.55859375, 0.83203125, 0.56663715839385986 ],
157+ "field_of_view" : 60.0,
158+ "front" : [ 0.27236083595988803, -0.25567329763523589, -0.92760484038816615 ],
159+ "lookat" : [ 2.4114965637897101, 1.8070288935660688, 1.5662280268112718 ],
160+ "up" : [ -0.072779625398507866, -0.96676294585190281, 0.24509698622097265 ],
161+ "zoom" : 0.47999999999999976
162+ }
163+ ],
164+ "version_major" : 1,
165+ "version_minor" : 0
166+ }
167+ """
168+ # write to json file
169+ view_json_file = "view.json"
170+ with open (view_json_file , 'w' ) as f :
171+ f .write (json_content )
172+ sample_ply_data = o3d .data .PLYPointCloud ()
173+ pcd = o3d .io .read_point_cloud (sample_ply_data .path )
174+ # 1. define
175+ viz = o3d .visualization .VisualizerWithKeyCallback ()
176+ # 2. create
177+ viz .create_window (window_name = "TEST ON Change View point through JSON, Press V Please" )
178+ # 3. add geometry
179+ viz .add_geometry (pcd )
180+ # 4. get control !!! must step by step
181+ ctr = viz .get_view_control ()
182+
183+ o3d_vctrl = ViewControl (ctr )
184+
185+ def set_view (viz ):
186+ #Your update routine
187+ o3d_vctrl .read_viewTfile (view_json_file )
188+ viz .update_renderer ()
189+ viz .poll_events ()
190+ viz .run ()
191+
192+ viz .register_key_callback (ord ('V' ), set_view )
193+ viz .run ()
194+ viz .destroy_window ()
195+ print ("\033 [92mAll o3d_view codes run successfully, Close now..\033 [0m See you!" )
196+
197+ # or:
198+ # viz = MyVisualizer(view_file, window_title="Check Pose")
199+ # viz.show([*pcds, *draw_tfs])
0 commit comments