2424
2525USAGE = inspect .cleandoc (
2626 """
27- Use the keys 1-5 to switch between displaying:
28- 1: color,
29- 2: semantic ground-truth annotations,
30- 3: instance ground-truth annotations,
31- 4: predicted semantic labels (see --semantic-label-file`, and
32- 5: predicted instance labels (see --instance-label-file`).
27+ Use the keys 1-7 to switch between displaying:
28+ 1: color given in ply file,
29+ 2: semantic ground-truth annotations given in label field in ply file,
30+ 3: instance ground-truth annotations given in label field in ply file,
31+ 4: additional (predicted) semantic labels given by `--semantic-label-filepath`,
32+ 5: additional (predicted) instance labels given by `--instance-label-filepath`,
33+ 6: additional (predicted) semantic labels given by `--panoptic-label-filepath`,
34+ 7: additional (predicted) instance labels given by `--panoptic-label-filepath`.
3335
3436 Press h to show further help and q to quit.
3537 """
@@ -53,10 +55,12 @@ def _parse_args():
5355 parser .add_argument (
5456 '--mode' ,
5557 type = str ,
56- choices = ('color' ,
57- 'semantic' , 'instance' ,
58- 'additional_semantic' , 'additional_instance' ),
59- default = 'color' ,
58+ choices = (
59+ 'ply_color' , 'ply_semantic' , 'ply_instance' ,
60+ 'additional_semantic' , 'additional_instance' ,
61+ 'additional_panoptic_semantic' , 'additional_panoptic_instance'
62+ ),
63+ default = 'ply_color' ,
6064 help = 'Render mode (use keys to switch)'
6165 )
6266 parser .add_argument (
@@ -87,12 +91,35 @@ def _parse_args():
8791 help = "Path to txt file containing the instance label for each vertex. "
8892 "Useful for visualizing predictions."
8993 )
94+ parser .add_argument (
95+ '--panoptic-label-filepath' ,
96+ type = str ,
97+ default = None ,
98+ help = "Path to txt file containing the panoptic label as sem*shift+ins "
99+ "for each vertex. See `--use-scannet-format` for 'shift'. Useful "
100+ "for visualizing predictions."
101+ )
90102 parser .add_argument (
91103 '--use-scannet-format' ,
92104 action = 'store_true' ,
93105 help = "If specified, labels will be taken as sem*1000+inst instead of "
94106 "our sem*(1<<16)+ins encoding."
95107 )
108+ parser .add_argument (
109+ '--use-panoptic-labels-as-instance-labels' ,
110+ action = 'store_true' ,
111+ help = "If specified, panoptic labels will be used as instance labels. "
112+ "This is useful if panoptic labels with 0-based instance ids per "
113+ "semantic class are given, which is common after panoptic "
114+ "merging. Consider using `--enumerate-instances` as well, to get "
115+ "smaller instance ids."
116+ )
117+ parser .add_argument (
118+ '--enumerate-instances' ,
119+ action = 'store_true' ,
120+ help = "If specified, instance labels will be enumerated before "
121+ "visualization. This is useful to get smaller instance ids. "
122+ )
96123
97124 # experimental
98125 parser .add_argument (
@@ -112,11 +139,20 @@ def _parse_args():
112139 return parser .parse_args ()
113140
114141
115- def _get_instance_colors (instance_labels , instance_cmap ):
116- # convert to continuos labels
117- # instance_labels = np.where(
118- # np.unique(instance_labels) == instance_labels[:, np.newaxis]
119- # )[1]
142+ def _get_instance_colors (instance_labels ,
143+ instance_cmap ,
144+ enumerate_instances = False ):
145+
146+ # enumerate instances
147+ if enumerate_instances :
148+ instance_labels_ = np .zeros_like (instance_labels ) # 0: no instance
149+ for new_id , old_id in enumerate (np .unique (instance_labels ), start = 1 ):
150+ if old_id == 0 :
151+ # no instance label
152+ continue
153+ instance_labels_ [instance_labels == old_id ] = new_id
154+ instance_labels = instance_labels_
155+
120156 if instance_labels .max () >= len (instance_cmap ):
121157 warnings .warn (
122158 f"Color map has only { len (instance_cmap )} colors, but the "
@@ -126,7 +162,51 @@ def _get_instance_colors(instance_labels, instance_cmap):
126162 return instance_cmap [instance_labels % len (instance_cmap )]
127163
128164
129- def _load_ply (filepath , semantic_cmap , instance_cmap , use_scannet_format = False ):
165+ def split_panoptic_labels (panoptic_labels ,
166+ use_scannet_format = False ,
167+ use_panoptic_as_instance = False ):
168+ if use_scannet_format :
169+ # uint16
170+ semantic_labels = panoptic_labels // 1000
171+ instance_labels = panoptic_labels - semantic_labels * 1000
172+ else :
173+ # uint32
174+ semantic_labels = panoptic_labels >> 16
175+ instance_labels = panoptic_labels & 0xFFFF
176+
177+ if not use_panoptic_as_instance :
178+ # check for same instance ids with multiple semantic classes (typically
179+ # happens when merging semantic and instance to panoptic labels)
180+ uniques , indices = np .unique (instance_labels , return_index = True )
181+ for instance_id , mask in zip (uniques , indices ):
182+ if instance_id == 0 :
183+ # no instance label
184+ continue
185+
186+ semantic_classes = np .unique (semantic_labels [mask ])
187+ if len (semantic_classes ) > 1 :
188+ # multiple semantic labels for the same instance id
189+ warnings .warn (
190+ f"Instance id { instance_id } has multiple semantic labels: "
191+ f"{ semantic_classes } . Consider adding "
192+ "`--use-panoptic-labels-as-instance-labels` to get unique "
193+ "instance ids and `--enumerate-instances` to get smaller "
194+ "instance ids."
195+ )
196+ break
197+ else :
198+ # use panoptic labels as instance labels
199+ instance_labels = panoptic_labels
200+
201+ return semantic_labels , instance_labels
202+
203+
204+ def _load_ply (filepath ,
205+ semantic_cmap ,
206+ instance_cmap ,
207+ use_scannet_format = False ,
208+ use_panoptic_as_instance = False ,
209+ enumerate_instances = False ):
130210 plydata = plyfile .PlyData .read (filepath )
131211
132212 num_verts = plydata ['vertex' ].count
@@ -150,29 +230,31 @@ def _load_ply(filepath, semantic_cmap, instance_cmap, use_scannet_format=False):
150230 if 'label' in plydata ['vertex' ]:
151231 labels = plydata ['vertex' ].data ['label' ] # uint16 / uint32
152232
153- if use_scannet_format :
154- # uint16
155- semantic_labels = labels // 1000
156- instance_labels = labels - semantic_labels * 1000
157- else :
158- # uint32
159- semantic_labels = labels >> 16
160- instance_labels = labels & 0xFFFF
233+ # split labels (panoptic / semantic-instance)
234+ semantic_labels , instance_labels = split_panoptic_labels (
235+ panoptic_labels = labels ,
236+ use_scannet_format = use_scannet_format ,
237+ use_panoptic_as_instance = use_panoptic_as_instance
238+ )
161239
162240 # semantic colors
163241 semantic_colors = semantic_cmap [semantic_labels ]
164242
165243 # instance colors
166- instance_colors = _get_instance_colors (instance_labels , instance_cmap )
244+ instance_colors = _get_instance_colors (
245+ instance_labels = instance_labels ,
246+ instance_cmap = instance_cmap ,
247+ enumerate_instances = enumerate_instances
248+ )
167249 else :
168250 # we do not have annotations
169251 instance_colors = np .zeros_like (colors )
170252 semantic_colors = np .zeros_like (colors )
171253
172254 labels = {
173- 'color ' : colors .astype ('float64' ) / 255 ,
174- 'instance ' : instance_colors .astype ('float64' ) / 255 ,
175- 'semantic ' : semantic_colors .astype ('float64' ) / 255
255+ 'ply_color ' : colors .astype ('float64' ) / 255 ,
256+ 'ply_instance ' : instance_colors .astype ('float64' ) / 255 ,
257+ 'ply_semantic ' : semantic_colors .astype ('float64' ) / 255
176258 }
177259
178260 pc .colors = o3d .utility .Vector3dVector (colors )
@@ -193,7 +275,9 @@ def main():
193275 filepath = args .filepath ,
194276 semantic_cmap = semantic_cmap ,
195277 instance_cmap = instance_cmap ,
196- use_scannet_format = args .use_scannet_format
278+ use_scannet_format = args .use_scannet_format ,
279+ use_panoptic_as_instance = args .use_panoptic_labels_as_instance_labels ,
280+ enumerate_instances = args .enumerate_instances
197281 )
198282 pc .colors = o3d .utility .Vector3dVector (labels [args .mode ])
199283
@@ -208,23 +292,41 @@ def main():
208292 # print usage
209293 print_section ("Usage" , USAGE )
210294
211- # load additional predicted labels
295+ # load additional ( predicted) labels
212296 if args .semantic_label_filepath is not None :
213297 semantic_labels = np .loadtxt (args .semantic_label_filepath ,
214298 dtype = np .uint64 )
215-
216299 assert len (semantic_labels ) == len (np .array (pc .points ))
217300
218301 labels ['additional_semantic' ] = semantic_cmap [semantic_labels ] / 255
219302
220303 if args .instance_label_filepath is not None :
221304 instance_labels = np .loadtxt (args .instance_label_filepath ,
222305 dtype = np .uint64 )
223-
224306 assert len (instance_labels ) == len (np .array (pc .points ))
225307
226308 labels ['additional_instance' ] = _get_instance_colors (
227- instance_labels , instance_cmap
309+ instance_labels = instance_labels ,
310+ instance_cmap = instance_cmap ,
311+ enumerate_instances = args .enumerate_instances
312+ ) / 255
313+
314+ if args .panoptic_label_filepath is not None :
315+ panoptic_labels = np .loadtxt (args .panoptic_label_filepath ,
316+ dtype = np .uint64 )
317+ assert len (panoptic_labels ) == len (np .array (pc .points ))
318+
319+ semantic_labels , instance_labels = split_panoptic_labels (
320+ panoptic_labels = panoptic_labels ,
321+ use_scannet_format = args .use_scannet_format ,
322+ use_panoptic_as_instance = args .use_panoptic_labels_as_instance_labels
323+ )
324+ labels ['additional_panoptic_semantic' ] = \
325+ semantic_cmap [semantic_labels ] / 255
326+ labels ['additional_panoptic_instance' ] = _get_instance_colors (
327+ instance_labels = instance_labels ,
328+ instance_cmap = instance_cmap ,
329+ enumerate_instances = args .enumerate_instances
228330 ) / 255
229331
230332 visualizer = o3d .visualization .VisualizerWithKeyCallback ()
@@ -248,17 +350,34 @@ def _func(vis):
248350 visualizer .add_geometry (pc )
249351
250352 # add callback for switching between modes
251- visualizer .register_key_callback (ord ('1' ),
252- _change_mode ('color' ))
253- visualizer .register_key_callback (ord ('2' ),
254- _change_mode ('semantic' ))
255- visualizer .register_key_callback (ord ('3' ),
256- _change_mode ('instance' ))
257- visualizer .register_key_callback (ord ('4' ),
258- _change_mode ('additional_semantic' ))
259- visualizer .register_key_callback (ord ('5' ),
260- _change_mode ('additional_instance' ))
261-
353+ visualizer .register_key_callback (
354+ ord ('1' ),
355+ _change_mode ('ply_color' )
356+ )
357+ visualizer .register_key_callback (
358+ ord ('2' ),
359+ _change_mode ('ply_semantic' )
360+ )
361+ visualizer .register_key_callback (
362+ ord ('3' ),
363+ _change_mode ('ply_instance' )
364+ )
365+ visualizer .register_key_callback (
366+ ord ('4' ),
367+ _change_mode ('additional_semantic' )
368+ )
369+ visualizer .register_key_callback (
370+ ord ('5' ),
371+ _change_mode ('additional_instance' )
372+ )
373+ visualizer .register_key_callback (
374+ ord ('6' ),
375+ _change_mode ('additional_panoptic_semantic' )
376+ )
377+ visualizer .register_key_callback (
378+ ord ('7' ),
379+ _change_mode ('additional_panoptic_instance' )
380+ )
262381 # experimental: visualize correspondences between the (grund-truth) point
263382 # cloud given by args.filepath and a second point cloud given by
264383 # args.second_pc_filepath, e.g., a mapped representation
0 commit comments