Skip to content

Commit 325dff0

Browse files
committed
Support continuous occupancy drawer and fix linting
1 parent bfd9050 commit 325dff0

File tree

3 files changed

+238
-14
lines changed

3 files changed

+238
-14
lines changed

embodiedscan/explorer.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import open3d as o3d
77

88
from embodiedscan.utils.color_selector import ColorMap
9-
from embodiedscan.utils.continuous_drawer import ContinuousDrawer
9+
from embodiedscan.utils.continuous_drawer import (ContinuousDrawer,
10+
ContinuousOccupancyDrawer)
1011
from embodiedscan.utils.img_drawer import ImageDrawer
1112

1213
DATASETS = ['scannet', '3rscan', 'matterport3d']
@@ -299,6 +300,54 @@ def render_countinuous_scene(self,
299300
pcd_downsample)
300301
drawer.begin()
301302

303+
def render_countinuous_occupancy(self, scene_name, start_cam=None):
304+
"""Render occupancy with continuous ego-centric observations.
305+
306+
Args:
307+
scene_name (str): Scene name.
308+
start_cam (str, optional): Camera frame from which the rendering
309+
starts. Defaults to None, corresponding to the first frame.
310+
"""
311+
s = scene_name.split('/')
312+
if len(s) == 2:
313+
dataset, region = s
314+
else:
315+
dataset, building, region = s
316+
317+
selected_scene = None
318+
start_idx = -1
319+
for scene in self.data:
320+
if scene['sample_idx'] == scene_name:
321+
selected_scene = scene
322+
if start_cam is not None:
323+
start_idx = -1
324+
for i, img in enumerate(scene['images']):
325+
img_path = img['img_path']
326+
if dataset == 'scannet':
327+
cam_name = img_path.split('/')[-1][:-4]
328+
elif dataset == '3rscan':
329+
cam_name = img_path.split('/')[-1][:-10]
330+
elif dataset == 'matterport3d':
331+
cam_name = img_path.split(
332+
'/')[-1][:-8] + img_path.split('/')[-1][-7:-4]
333+
if cam_name == start_cam:
334+
start_idx = i
335+
break
336+
if start_idx == -1:
337+
print('No such camera')
338+
return
339+
else:
340+
start_idx = 0
341+
342+
if selected_scene is None:
343+
print('No such scene')
344+
return
345+
346+
drawer = ContinuousOccupancyDrawer(dataset, self.dataroot[dataset],
347+
selected_scene, self.classes,
348+
self.color_selector, start_idx)
349+
drawer.begin()
350+
302351
def render_occupancy(self, scene_name):
303352
"""Render the occupancy annotation of a given scene.
304353

embodiedscan/structures/__init__.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
# Copyright (c) OpenMMLab. All rights reserved.
2-
from .bbox_3d import (BaseInstance3DBoxes, Box3DMode, # CameraInstance3DBoxes,
3-
Coord3DMode, # DepthInstance3DBoxes, LiDARInstance3DBoxes,
4-
EulerInstance3DBoxes, # EulerCameraInstance3DBoxes, EulerDepthInstance3DBoxes,
5-
get_box_type, get_proj_mat_by_coord_type, limit_period,
2+
from .bbox_3d import (BaseInstance3DBoxes, Box3DMode, Coord3DMode,
3+
EulerInstance3DBoxes, get_box_type,
4+
get_proj_mat_by_coord_type, limit_period,
65
mono_cam_box2vis, points_cam2img, points_img2cam,
76
rotation_3d_in_axis, rotation_3d_in_euler, xywhr2xyxyr)
87

98
__all__ = [
10-
'BaseInstance3DBoxes', 'Box3DMode', # 'CameraInstance3DBoxes',
11-
'Coord3DMode', # 'DepthInstance3DBoxes', 'LiDARInstance3DBoxes',
12-
'EulerInstance3DBoxes', # 'EulerDepthInstance3DBoxes', 'EulerCameraInstance3DBoxes',
9+
'BaseInstance3DBoxes', 'Box3DMode', 'Coord3DMode', 'EulerInstance3DBoxes',
1310
'get_box_type', 'get_proj_mat_by_coord_type', 'limit_period',
1411
'mono_cam_box2vis', 'points_cam2img', 'points_img2cam',
1512
'rotation_3d_in_axis', 'rotation_3d_in_euler', 'xywhr2xyxyr'
16-
]
13+
]

embodiedscan/utils/continuous_drawer.py

+183-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22

33
import cv2
4+
import mmengine
45
import numpy as np
56
import open3d as o3d
67

@@ -39,13 +40,16 @@ def __init__(self, dataset, dir, scene, classes, color_selector, start_idx,
3940
self.vis.register_key_callback(262, self.draw_next) # Right Arrow
4041
self.vis.register_key_callback(ord('D'), self.draw_next)
4142
self.vis.register_key_callback(ord('N'), self.draw_next)
43+
self.vis.register_key_callback(256, self.close)
4244

4345
def begin(self):
4446
"""Some preparations before starting the rendering."""
4547
print('Press N/D/Right Arrow to draw next frame.')
4648
print('Press Q to close the window and quit.')
47-
print('Please wait for a few seconds after rendering some frames,',
48-
'or the program may crash.')
49+
print("When you've rendered a lot of frames, the exit can become",
50+
'very slow because the program needs time to free up space.')
51+
print('You can also press Esc to close window immediately,',
52+
'which may result in a segmentation fault.')
4953
s = self.scene['sample_idx'].split('/')
5054
self.occupied = np.zeros((len(self.scene['instances']), ), dtype=bool)
5155
if len(s) == 2:
@@ -115,9 +119,12 @@ def draw_next(self, vis):
115119
pc.colors = o3d.utility.Vector3dVector(colors[::self.downsample])
116120
vis.add_geometry(pc)
117121
if self.camera is not None:
118-
vis.remove_geometry(self.camera)
119-
self.camera = draw_camera(extrinsic)
120-
vis.add_geometry(self.camera)
122+
cam_points = draw_camera(extrinsic, return_points=True)
123+
self.camera.points = cam_points
124+
vis.update_geometry(self.camera)
125+
else:
126+
self.camera = draw_camera(extrinsic)
127+
vis.add_geometry(self.camera)
121128

122129
for ins_idx in img['visible_instance_ids']:
123130
if self.occupied[ins_idx]:
@@ -135,3 +142,174 @@ def draw_next(self, vis):
135142
vis.update_renderer()
136143
vis.poll_events()
137144
vis.run()
145+
146+
def close(self, vis):
147+
"""Close the visualizer.
148+
149+
Args:
150+
vis (open3d.visualization.VisualizerWithKeyCallback): Visualizer.
151+
"""
152+
vis.clear_geometries()
153+
vis.destroy_window()
154+
vis.close()
155+
156+
157+
class ContinuousOccupancyDrawer:
158+
"""Visualization tool for Continuous Occupancy Prediction task.
159+
160+
This class serves as the API for visualizing Continuous 3D Object
161+
Detection task.
162+
163+
Args:
164+
dataset (str): Name of composed raw dataset, one of
165+
scannet/3rscan/matterport3d.
166+
dir (str): Root path of the dataset.
167+
scene (dict): Annotation of the selected scene.
168+
classes (list): Class information.
169+
color_selector (ColorMap): ColorMap for visualization.
170+
start_idx (int) : Index of the frame which the task starts.
171+
"""
172+
173+
def __init__(self, dataset, dir, scene, classes, color_selector,
174+
start_idx):
175+
self.dir = dir
176+
self.dataset = dataset
177+
self.scene = scene
178+
self.classes = classes
179+
self.color_selector = color_selector
180+
self.idx = start_idx
181+
self.camera = None
182+
183+
if dataset == 'matterport3d':
184+
_, building, region = scene['sample_idx'].split('/')
185+
else:
186+
_, region = scene['sample_idx'].split('/')
187+
188+
if dataset == 'scannet':
189+
self.occ_path = os.path.join(self.dir, 'scans', region,
190+
'occupancy', 'occupancy.npy')
191+
self.mask_path = os.path.join(self.dir, 'scans', region,
192+
'occupancy', 'visible_occupancy.pkl')
193+
elif dataset == '3rscan':
194+
self.occ_path = os.path.join(self.dir, region, 'occupancy',
195+
'occupancy.npy')
196+
self.mask_path = os.path.join(self.dir, region, 'occupancy',
197+
'visible_occupancy.pkl')
198+
elif dataset == 'matterport3d':
199+
self.occ_path = os.path.join(self.dir, building, 'occupancy',
200+
f'occupancy_{region}.npy')
201+
self.mask_path = os.path.join(self.dir, building, 'occupancy',
202+
f'visible_occupancy_{region}.pkl')
203+
else:
204+
raise NotImplementedError
205+
206+
self.occupied = np.zeros((len(self.scene['instances']), ), dtype=bool)
207+
self.vis = o3d.visualization.VisualizerWithKeyCallback()
208+
self.vis.register_key_callback(262, self.draw_next) # Right Arrow
209+
self.vis.register_key_callback(ord('D'), self.draw_next)
210+
self.vis.register_key_callback(ord('N'), self.draw_next)
211+
self.vis.register_key_callback(256, self.close)
212+
213+
def begin(self):
214+
"""Some preparations before starting the rendering."""
215+
print('Press N/D/Right Arrow to draw next frame.')
216+
print('Press Q to close the window and quit.')
217+
print("When you've rendered a lot of frames, the exit can become",
218+
'very slow because the program needs time to free up space.')
219+
print('You can also press Esc to close window immediately,',
220+
'which may result in a segmentation fault.')
221+
self.gt = np.load(self.occ_path)
222+
self.mask = mmengine.load(self.mask_path)
223+
224+
point_cloud_range = [-3.2, -3.2, -1.28 + 0.5, 3.2, 3.2, 1.28 + 0.5]
225+
occ_size = [40, 40, 16]
226+
self.grid_size = 0.16
227+
228+
self.points = np.zeros((self.gt.shape[0], 6), dtype=float)
229+
self.gird_id = np.ones(occ_size, dtype=int) * -1
230+
self.visible_mask = np.zeros((self.gt.shape[0], ), dtype=bool)
231+
for i in range(self.gt.shape[0]):
232+
x, y, z, label_id = self.gt[i]
233+
self.gird_id[x, y, z] = i
234+
label_id = int(label_id)
235+
if label_id == 0:
236+
label = 'object'
237+
else:
238+
label = self.classes[label_id - 1]
239+
color = self.color_selector.get_color(label)
240+
color = [x / 255.0 for x in color]
241+
self.points[i][:3] = [
242+
x * self.grid_size + point_cloud_range[0] + self.grid_size / 2,
243+
y * self.grid_size + point_cloud_range[1] + self.grid_size / 2,
244+
z * self.grid_size + point_cloud_range[2] + self.grid_size / 2
245+
]
246+
self.points[i][3:] = color
247+
248+
pcd = o3d.geometry.PointCloud()
249+
pcd.points = o3d.utility.Vector3dVector(self.points[:, :3])
250+
pcd.colors = o3d.utility.Vector3dVector(self.points[:, 3:])
251+
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(
252+
pcd, voxel_size=self.grid_size)
253+
frame = o3d.geometry.TriangleMesh.create_coordinate_frame()
254+
self.vis.create_window()
255+
self.vis.add_geometry(voxel_grid)
256+
self.vis.add_geometry(frame)
257+
ctr = self.vis.get_view_control()
258+
self.view_param = ctr.convert_to_pinhole_camera_parameters()
259+
self.voxel_grid = voxel_grid
260+
self.draw_next(self.vis)
261+
262+
def draw_next(self, vis):
263+
"""Render the next frame.
264+
265+
Args:
266+
vis (open3d.visualization.VisualizerWithKeyCallback): Visualizer.
267+
"""
268+
if self.idx >= len(self.scene['images']):
269+
print('No more images')
270+
return
271+
272+
img = self.scene['images'][self.idx]
273+
extrinsic = self.scene['axis_align_matrix'] @ img['cam2global']
274+
275+
mask = self.mask[self.idx]['visible_occupancy']
276+
visible_ids = np.unique(self.gird_id[mask])
277+
visible_ids = visible_ids[visible_ids >= 0]
278+
self.visible_mask[visible_ids] = True
279+
pcd = o3d.geometry.PointCloud()
280+
pcd.points = o3d.utility.Vector3dVector(
281+
self.points[self.visible_mask][:, :3])
282+
pcd.colors = o3d.utility.Vector3dVector(
283+
self.points[self.visible_mask][:, 3:])
284+
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(
285+
pcd, voxel_size=self.grid_size)
286+
287+
if self.camera is not None:
288+
cam_points = draw_camera(extrinsic, return_points=True)
289+
self.camera.points = cam_points
290+
vis.update_geometry(self.camera)
291+
else:
292+
self.camera = draw_camera(extrinsic)
293+
vis.add_geometry(self.camera)
294+
295+
self.voxel_grid.clear()
296+
vis.update_geometry(self.voxel_grid)
297+
vis.remove_geometry(self.voxel_grid)
298+
vis.add_geometry(voxel_grid)
299+
self.voxel_grid = voxel_grid
300+
self.idx += 1
301+
ctr = vis.get_view_control()
302+
ctr.convert_from_pinhole_camera_parameters(self.view_param)
303+
vis.update_renderer()
304+
vis.poll_events()
305+
vis.run()
306+
307+
def close(self, vis):
308+
"""Close the visualizer.
309+
310+
Args:
311+
vis (open3d.visualization.VisualizerWithKeyCallback): Visualizer.
312+
"""
313+
vis.clear_geometries()
314+
vis.destroy_window()
315+
vis.close()

0 commit comments

Comments
 (0)