bulatko commited on
Commit
55e58d1
·
1 Parent(s): 4eeefd1

adding real MK

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. MaskClustering +0 -1
  2. MaskClustering/arkit_gt_prep.py +154 -0
  3. MaskClustering/arkit_prep.py +137 -0
  4. MaskClustering/arkit_vggt_prep.py +154 -0
  5. MaskClustering/bins_build_pkl.py +36 -0
  6. MaskClustering/build_pkl.py +36 -0
  7. MaskClustering/cluster_masks.py +83 -0
  8. MaskClustering/configs/arkit_dust3r_posed.json +10 -0
  9. MaskClustering/configs/arkit_gt.json +10 -0
  10. MaskClustering/configs/arkit_gt_train.json +10 -0
  11. MaskClustering/configs/arkit_vggt.json +10 -0
  12. MaskClustering/configs/demo.json +10 -0
  13. MaskClustering/configs/itw.json +10 -0
  14. MaskClustering/configs/matterport3d.json +10 -0
  15. MaskClustering/configs/scannet.json +10 -0
  16. MaskClustering/configs/scannet_dust3r_posed_15.json +10 -0
  17. MaskClustering/configs/scannet_dust3r_posed_25.json +10 -0
  18. MaskClustering/configs/scannet_dust3r_posed_35.json +10 -0
  19. MaskClustering/configs/scannet_dust3r_posed_35_bulat.json +10 -0
  20. MaskClustering/configs/scannet_dust3r_posed_45.json +10 -0
  21. MaskClustering/configs/scannet_dust3r_posed_45_andrey.json +10 -0
  22. MaskClustering/configs/scannet_dust3r_posed_45_bulat.json +10 -0
  23. MaskClustering/configs/scannet_dust3r_unposed_15.json +10 -0
  24. MaskClustering/configs/scannet_dust3r_unposed_25.json +10 -0
  25. MaskClustering/configs/scannet_dust3r_unposed_35.json +10 -0
  26. MaskClustering/configs/scannet_dust3r_unposed_45.json +10 -0
  27. MaskClustering/configs/scannetpp.json +10 -0
  28. MaskClustering/configs/scannetpp_dust3r_filtered_depth.json +10 -0
  29. MaskClustering/configs/scannetpp_dust3r_posed.json +10 -0
  30. MaskClustering/configs/scannetpp_dust3r_unposed.json +10 -0
  31. MaskClustering/configs/scannetpp_mapanything_posed.json +10 -0
  32. MaskClustering/configs/scannetpp_v2_dust3r_posed.json +10 -0
  33. MaskClustering/configs/scannetpp_v2_dust3r_unposed.json +10 -0
  34. MaskClustering/configs/wild.json +10 -0
  35. MaskClustering/dataset/demo.py +100 -0
  36. MaskClustering/dataset/matterport.py +137 -0
  37. MaskClustering/dataset/scannet.py +452 -0
  38. MaskClustering/dataset/scannetpp.py +217 -0
  39. MaskClustering/dense_masks.py +91 -0
  40. MaskClustering/evaluation/__init__.py +1 -0
  41. MaskClustering/evaluation/constants.py +78 -0
  42. MaskClustering/evaluation/evaluate.py +420 -0
  43. MaskClustering/evaluation/utils_3d.py +66 -0
  44. MaskClustering/infer_single_scene.py +355 -0
  45. MaskClustering/main.py +30 -0
  46. MaskClustering/make_bins.py +54 -0
  47. MaskClustering/make_pkl.py +392 -0
  48. MaskClustering/make_pkl_arkit.py +349 -0
  49. MaskClustering/make_pkl_conf.py +295 -0
  50. MaskClustering/mask_predict.py +114 -0
MaskClustering DELETED
@@ -1 +0,0 @@
1
- ../Indoor/MaskClustering/
 
 
MaskClustering/arkit_gt_prep.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from pathlib import Path
3
+ import torch
4
+ import cv2
5
+ import numpy as np
6
+ import os
7
+ import open3d as o3d
8
+ from tqdm import tqdm
9
+
10
+ def process_scene(scene_params, target_depth_shape=None):
11
+ images = scene_params["image_files"]
12
+ poses = scene_params["poses"]
13
+ depths = scene_params["depths"]
14
+ Ks = scene_params["Ks"]
15
+ pts3d = scene_params["pts3d"]
16
+ im_confs = scene_params["im_conf"]
17
+ print(scene_params.keys())
18
+ im_shapes = scene_params["imshapes"]
19
+ im_shape = im_shapes[0]
20
+
21
+ image_hw = cv2.imread(images[0]).shape[:2]
22
+ image_scale = np.ones((3, 3))
23
+ image_scale[0] *= image_hw[1] / im_shape[1]
24
+ image_scale[1] *= image_hw[0] / im_shape[0]
25
+
26
+ if target_depth_shape is None:
27
+ target_depth_shape = image_hw
28
+
29
+
30
+ depth_scale = np.ones((3, 3))
31
+ depth_scale[0] *= target_depth_shape[1] / im_shape[1]
32
+ depth_scale[1] *= target_depth_shape[0] / im_shape[0]
33
+
34
+ data = [
35
+ {
36
+ "image_path": image,
37
+ "pose": pose,
38
+ "depth": cv2.resize(depth.numpy(), target_depth_shape[::-1], interpolation=cv2.INTER_LINEAR),
39
+ "source_K": K,
40
+ "image_K": K * image_scale,
41
+ "depth_K": K * depth_scale,
42
+ "pts3d": pts,
43
+ "im_conf": im_conf,
44
+ "im_shape_target": image_hw,
45
+ "depth_shape_target": target_depth_shape,
46
+ "shape_original": im_shape,
47
+ } for image, pose, depth, K, pts, im_conf in zip(images, poses, depths, Ks, pts3d, im_confs)
48
+ ]
49
+
50
+ return data
51
+
52
+
53
+ def export_scene(scene_id, data, processing_args):
54
+ out_path = processing_args["out_dir"] / scene_id
55
+ K_color = data[0]["image_K"]
56
+ K_depth = data[0]["depth_K"]
57
+
58
+ def proc_k(K):
59
+ res = np.eye(4)
60
+ res[:3, :3] = K[:3, :3]
61
+ return res
62
+
63
+ K_color = proc_k(K_color)
64
+ K_depth = proc_k(K_depth)
65
+ intrinsics_path = out_path / "intrinsic"
66
+ intrinsics_path.mkdir(parents=True, exist_ok=True)
67
+ np.savetxt(intrinsics_path / "intrinsic_color.txt", K_color)
68
+ np.savetxt(intrinsics_path / "intrinsic_depth.txt", K_depth)
69
+
70
+ np.savetxt(intrinsics_path / "extrinsic_color.txt", np.eye(4))
71
+ np.savetxt(intrinsics_path / "extrinsic_depth.txt", np.eye(4))
72
+
73
+ for i, item in enumerate(data):
74
+ img_name = Path(item["image_path"]).stem
75
+ image_path = out_path / "color" / f"{img_name}.jpg"
76
+ image_path.parent.mkdir(parents=True, exist_ok=True)
77
+ try:
78
+ os.symlink(item["image_path"], image_path)
79
+ except FileExistsError:
80
+ pass
81
+
82
+
83
+ depth_path = out_path / "depth" / f"{img_name}.png"
84
+ depth_path.parent.mkdir(parents=True, exist_ok=True)
85
+ try:
86
+ os.symlink(item["depth_path"], depth_path)
87
+ except FileExistsError:
88
+ pass
89
+
90
+ pose_path = out_path / "pose" / f"{img_name}.txt"
91
+ pose_path.parent.mkdir(parents=True, exist_ok=True)
92
+ np.savetxt(pose_path, item["pose"])
93
+
94
+ try:
95
+ os.symlink(item["pts3d_path"], out_path / f"{scene_id}_vh_clean_2.ply")
96
+ except FileExistsError:
97
+ pass
98
+
99
+
100
+
101
+
102
+
103
+
104
+ processing_args = {
105
+ "confidence_threshold": 1,
106
+ "voxel_size": 0.025,
107
+ "out_dir": Path("data/arkit_gt_train/processed"),
108
+ }
109
+
110
+
111
+ val_path = Path("../") / "OKNO/data/arkitscenes/arkitscenes_offline_infos_train.pkl"
112
+ out_dir = Path("data/arkit_gt/processed")
113
+ with open(val_path, "rb") as f:
114
+ data = pickle.load(f)
115
+
116
+ data_list = data["data_list"]
117
+ val_scenes = [scene["lidar_points"]["lidar_path"] for scene in data_list][:2500]
118
+ def extract_name(item):
119
+ return item.split("_")[0]
120
+ val_scenes = [extract_name(scene) for scene in val_scenes]
121
+
122
+ scenes_path = Path("/workspace-SR006.nfs2/datasets/arkitscenes/offline_prepared_data/posed_images")
123
+ pcd_path = Path("/workspace-SR006.nfs2/datasets/arkit_data/3dod/Training/")
124
+ scene = val_scenes[0]
125
+ num_images = 160
126
+
127
+
128
+ for scene in tqdm(val_scenes):
129
+ try:
130
+ if (processing_args["out_dir"] / scene).exists():
131
+ continue
132
+ scene_path = scenes_path / scene
133
+
134
+ colors = sorted(scene_path.glob("*.jpg"))
135
+ if len(colors) > num_images:
136
+ indices = np.linspace(0, len(colors) - 1, num_images).astype(int)
137
+ colors = [colors[i] for i in indices]
138
+ depths = [a.parent / (a.stem + ".png") for a in colors]
139
+ poses = [a.parent / (a.stem + ".txt") for a in colors]
140
+ K = np.loadtxt(scene_path / "intrinsic.txt")
141
+
142
+ scene_params = [{
143
+ "image_path": image,
144
+ "pose": np.loadtxt(pose),
145
+ "depth_path": depth,
146
+ "source_K": K,
147
+ "image_K": K,
148
+ "depth_K": K,
149
+ "pts3d_path": pcd_path / scene / f"{scene}_3dod_mesh.ply",
150
+ } for image, depth, pose in zip(colors, depths, poses)]
151
+ export_scene(scene, scene_params, processing_args)
152
+ except Exception as e:
153
+ print(e)
154
+ continue
MaskClustering/arkit_prep.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from pathlib import Path
3
+ import torch
4
+ import cv2
5
+ import numpy as np
6
+ import os
7
+ import open3d as o3d
8
+ from tqdm import tqdm
9
+
10
+ def process_scene(scene_params, target_depth_shape=None):
11
+ images = scene_params["image_files"]
12
+ poses = scene_params["poses"]
13
+ depths = scene_params["depths"]
14
+ Ks = scene_params["Ks"]
15
+ pts3d = scene_params["pts3d"]
16
+ im_confs = scene_params["im_conf"]
17
+ print(scene_params.keys())
18
+ im_shapes = scene_params["imshapes"]
19
+ im_shape = im_shapes[0]
20
+
21
+ image_hw = cv2.imread(images[0]).shape[:2]
22
+ image_scale = np.ones((3, 3))
23
+ image_scale[0] *= image_hw[1] / im_shape[1]
24
+ image_scale[1] *= image_hw[0] / im_shape[0]
25
+
26
+ if target_depth_shape is None:
27
+ target_depth_shape = image_hw
28
+
29
+
30
+ depth_scale = np.ones((3, 3))
31
+ depth_scale[0] *= target_depth_shape[1] / im_shape[1]
32
+ depth_scale[1] *= target_depth_shape[0] / im_shape[0]
33
+
34
+ data = [
35
+ {
36
+ "image_path": image,
37
+ "pose": pose,
38
+ "depth": cv2.resize(depth.numpy(), target_depth_shape[::-1], interpolation=cv2.INTER_LINEAR),
39
+ "source_K": K,
40
+ "image_K": K * image_scale,
41
+ "depth_K": K * depth_scale,
42
+ "pts3d": pts,
43
+ "im_conf": im_conf,
44
+ "im_shape_target": image_hw,
45
+ "depth_shape_target": target_depth_shape,
46
+ "shape_original": im_shape,
47
+ } for image, pose, depth, K, pts, im_conf in zip(images, poses, depths, Ks, pts3d, im_confs)
48
+ ]
49
+
50
+ return data
51
+
52
+
53
+ def export_scene(scene_id, scene_params, processing_args):
54
+ data = process_scene(scene_params)
55
+ out_path = processing_args["out_dir"] / scene_id
56
+ K_color = data[0]["image_K"]
57
+ K_depth = data[0]["depth_K"]
58
+
59
+ def proc_k(K):
60
+ res = np.eye(4)
61
+ res[:3, :3] = K[:3, :3]
62
+ return res
63
+
64
+ K_color = proc_k(K_color)
65
+ K_depth = proc_k(K_depth)
66
+ intrinsics_path = out_path / "intrinsic"
67
+ intrinsics_path.mkdir(parents=True, exist_ok=True)
68
+ np.savetxt(intrinsics_path / "intrinsic_color.txt", K_color)
69
+ np.savetxt(intrinsics_path / "intrinsic_depth.txt", K_depth)
70
+
71
+ np.savetxt(intrinsics_path / "extrinsic_color.txt", np.eye(4))
72
+ np.savetxt(intrinsics_path / "extrinsic_depth.txt", np.eye(4))
73
+ all_pts = []
74
+ all_colors = []
75
+ for i, item in enumerate(data):
76
+ img_name = Path(item["image_path"]).stem
77
+ image_path = out_path / "color" / f"{img_name}.jpg"
78
+ image_path.parent.mkdir(parents=True, exist_ok=True)
79
+ try:
80
+ os.symlink(item["image_path"], image_path)
81
+ except FileExistsError:
82
+ pass
83
+ image = cv2.imread(item["image_path"])
84
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
85
+ image = cv2.resize(image, item['shape_original'][::-1]) / 255.
86
+
87
+
88
+ depth_path = out_path / "depth" / f"{img_name}.png"
89
+ depth_path.parent.mkdir(parents=True, exist_ok=True)
90
+ cv2.imwrite(depth_path, (item["depth"] * 1000).astype(np.uint16))
91
+
92
+ pose_path = out_path / "pose" / f"{img_name}.txt"
93
+ pose_path.parent.mkdir(parents=True, exist_ok=True)
94
+ np.savetxt(pose_path, item["pose"])
95
+ pts = item["pts3d"][item["im_conf"] > processing_args["confidence_threshold"]]
96
+ image = image[item["im_conf"] > processing_args["confidence_threshold"]]
97
+
98
+ all_pts.append(pts.view(-1, 3))
99
+ all_colors.append(image.reshape(-1, 3))
100
+ all_pts = np.concatenate(all_pts, axis=0)
101
+ all_colors = np.concatenate(all_colors, axis=0)
102
+
103
+ pcd = o3d.geometry.PointCloud()
104
+ pcd.points = o3d.utility.Vector3dVector(all_pts)
105
+ pcd.colors = o3d.utility.Vector3dVector(all_colors)
106
+ pcd = pcd.voxel_down_sample(voxel_size=processing_args["voxel_size"])
107
+ o3d.io.write_point_cloud(out_path / f"{scene_id}_vh_clean_2.ply", pcd)
108
+
109
+
110
+
111
+
112
+
113
+
114
+ processing_args = {
115
+ "confidence_threshold": 1,
116
+ "voxel_size": 0.025,
117
+ "out_dir": Path("data/arkit_dust3r_posed/processed"),
118
+ }
119
+
120
+
121
+ val_path = Path("../") / "OKNO/data/arkitscenes/arkitscenes_offline_infos_val.pkl"
122
+ out_dir = Path("data/arkit_dust3r_posed/processed")
123
+ with open(val_path, "rb") as f:
124
+ data = pickle.load(f)
125
+
126
+ data_list = data["data_list"]
127
+ val_scenes = [scene["lidar_points"]["lidar_path"] for scene in data_list]
128
+ def extract_name(item):
129
+ return item.split("_")[0]
130
+ val_scenes = [extract_name(scene) for scene in val_scenes]
131
+
132
+ dut3r_path = Path("/home/jovyan/users/lemeshko/Indoor/DUSt3R/res/arkit_posed")
133
+
134
+ for scene in tqdm(val_scenes):
135
+ scene_path = dut3r_path / scene
136
+ scene_params = torch.load(scene_path / "scene_params.pt")
137
+ export_scene(scene, scene_params, processing_args)
MaskClustering/arkit_vggt_prep.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from pathlib import Path
3
+ import torch
4
+ import cv2
5
+ import numpy as np
6
+ import os
7
+ import open3d as o3d
8
+ from tqdm import tqdm
9
+ from tqdm.contrib.concurrent import process_map, thread_map
10
+ from rec_utils.datasets import ARKitDataset, VGGTDataset
11
+ from rec_utils.aligner import build_aligner_1p1d
12
+ from rec_utils.datasets.arkit.utils import rotate_image
13
+
14
+ def process_scene(scene_params, target_depth_shape=None):
15
+ images = scene_params["image_files"]
16
+ poses = scene_params["poses"]
17
+ depths = scene_params["depths"]
18
+ Ks = scene_params["Ks"]
19
+ pts3d = scene_params["pts3d"]
20
+ im_confs = scene_params["im_conf"]
21
+ print(scene_params.keys())
22
+ im_shapes = scene_params["imshapes"]
23
+ im_shape = im_shapes[0]
24
+
25
+ image_hw = cv2.imread(images[0]).shape[:2]
26
+ image_scale = np.ones((3, 3))
27
+ image_scale[0] *= image_hw[1] / im_shape[1]
28
+ image_scale[1] *= image_hw[0] / im_shape[0]
29
+
30
+ if target_depth_shape is None:
31
+ target_depth_shape = image_hw
32
+
33
+
34
+ depth_scale = np.ones((3, 3))
35
+ depth_scale[0] *= target_depth_shape[1] / im_shape[1]
36
+ depth_scale[1] *= target_depth_shape[0] / im_shape[0]
37
+
38
+ data = [
39
+ {
40
+ "image_path": image,
41
+ "pose": pose,
42
+ "depth": cv2.resize(depth.numpy(), target_depth_shape[::-1], interpolation=cv2.INTER_LINEAR),
43
+ "source_K": K,
44
+ "image_K": K * image_scale,
45
+ "depth_K": K * depth_scale,
46
+ "pts3d": pts,
47
+ "im_conf": im_conf,
48
+ "im_shape_target": image_hw,
49
+ "depth_shape_target": target_depth_shape,
50
+ "shape_original": im_shape,
51
+ } for image, pose, depth, K, pts, im_conf in zip(images, poses, depths, Ks, pts3d, im_confs)
52
+ ]
53
+
54
+ return data
55
+
56
+ def es_wrap(data):
57
+ try:
58
+ return export_scene(data)
59
+ except Exception as e:
60
+ print(e)
61
+ return None
62
+
63
+ def export_scene(data):
64
+ vggt_dataset, arkit_dataset, i, out_dir, processing_args = data
65
+ vggt_scene = vggt_dataset[i]
66
+ scene_id = vggt_scene.id
67
+ arkit_scene = arkit_dataset[scene_id]
68
+ out_path = out_dir / scene_id
69
+ out_path.mkdir(parents=True, exist_ok=True)
70
+ # if os.path.exists(out_path / f'{vggt_scene.id}_vh_clean_2.ply'):
71
+ # return
72
+ arkit_scene.frames = arkit_scene.frames[-100:]
73
+ aligner = build_aligner_1p1d(source_scene=vggt_scene, target_scene=arkit_scene)
74
+ scene = aligner.align(vggt_scene, inplace=True)
75
+ # scene = arkit_scene
76
+
77
+ K_color = arkit_scene[0].image_intrinsics
78
+ K_depth = arkit_scene[0].depth_intrinsics
79
+ intrinsics_path = out_path / "intrinsic"
80
+ intrinsics_path.mkdir(parents=True, exist_ok=True)
81
+ color_path = out_path / "color"
82
+ color_path.mkdir(parents=True, exist_ok=True)
83
+ depth_path = out_path / "depth"
84
+ depth_path.mkdir(parents=True, exist_ok=True)
85
+ pose_path = out_path / "pose"
86
+ pose_path.mkdir(parents=True, exist_ok=True)
87
+ np.savetxt(intrinsics_path / "intrinsic_color.txt", K_color)
88
+ np.savetxt(intrinsics_path / "intrinsic_depth.txt", K_depth)
89
+
90
+ np.savetxt(intrinsics_path / "extrinsic_color.txt", np.eye(4))
91
+ np.savetxt(intrinsics_path / "extrinsic_depth.txt", np.eye(4))
92
+ tsdffusion = o3d.pipelines.integration.ScalableTSDFVolume(
93
+ voxel_length=0.025,
94
+ sdf_trunc=0.1,
95
+ color_type=o3d.pipelines.integration.TSDFVolumeColorType.RGB8
96
+ )
97
+ print("rotation angle", arkit_scene.rotation_angle)
98
+ for frame in tqdm(scene.frames):
99
+ # image = rotate_image(frame.image, arkit_scene.rotation_angle)
100
+ # # image = frame.image
101
+ # h, w = image.shape[:2]
102
+ # depth = frame.depth.astype(np.float32)
103
+ # depth = cv2.resize(depth, (w, h), interpolation=cv2.INTER_LINEAR)
104
+ # color = o3d.geometry.Image(image)
105
+ np.savetxt(str(pose_path / f'{frame.frame_id}.txt'), frame.pose)
106
+ # cv2.imwrite(str(color_path / f'{frame.frame_id}.jpg'), image[..., ::-1])
107
+ # cv2.imwrite(str(depth_path / f'{frame.frame_id}.png'), (depth * 1000.).astype(np.uint16))
108
+
109
+ # fx, fy = K_color[0, 0], K_color[1, 1]
110
+ # cx, cy = K_color[0, 2], K_color[1, 2]
111
+
112
+ # dh, dw = depth.shape
113
+ # depth_o3d = o3d.geometry.Image(depth)
114
+
115
+ # rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(
116
+ # color, depth_o3d, depth_trunc=10., convert_rgb_to_intensity=False, depth_scale=1.0
117
+ # )
118
+ # camera_o3d = o3d.camera.PinholeCameraIntrinsic(w, h, fx, fy, cx, cy)
119
+ # tsdffusion.integrate(
120
+ # rgbd, camera_o3d,
121
+ # np.linalg.inv(frame.pose),
122
+ # )
123
+ # pc = tsdffusion.extract_point_cloud()
124
+ # pc.voxel_down_sample(voxel_size=0.025)
125
+ # o3d.io.write_point_cloud(str(out_path / f'{scene.id}_vh_clean_2.ply'), pc)
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+ vggt_dataset = VGGTDataset("/home/jovyan/users/bulat/workspace/3drec/vggt/output/arkit_new/")
134
+ arkit_dataset = ARKitDataset("/workspace-SR006.nfs2/datasets/arkitscenes/offline_prepared_data/posed_images/")
135
+ processing_args = {
136
+ "voxel_size": 0.025,
137
+ "out_dir": Path("data/arkit_dust3r_posed/processed"),
138
+ }
139
+
140
+
141
+ val_path = Path("../") / "OKNO/data/arkitscenes/arkitscenes_offline_infos_val.pkl"
142
+ out_dir = Path("data/arkit_vggt/processed")
143
+ with open(val_path, "rb") as f:
144
+ data = pickle.load(f)
145
+
146
+ data_list = data["data_list"]
147
+ val_scenes = [scene["lidar_points"]["lidar_path"] for scene in data_list]
148
+ def extract_name(item):
149
+ return item.split("_")[0]
150
+ val_scenes = [extract_name(scene) for scene in val_scenes]
151
+ data = [(vggt_dataset, arkit_dataset, i, out_dir, processing_args) for i in range(len(vggt_dataset))]
152
+ thread_map(es_wrap, data, chunksize=128)
153
+ # break
154
+
MaskClustering/bins_build_pkl.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+ from tqdm.auto import tqdm
4
+ from copy import deepcopy
5
+
6
+
7
+
8
+ if __name__ == "__main__":
9
+ pred_path = \
10
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/arkitscenes/points_vggt/"
11
+ out_pkl_path = \
12
+ "arkit_vggt_val_subset.pkl"
13
+ gt_pkl_path = \
14
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/arkitscenes/arkitscenes_offline_infos_val.pkl"
15
+
16
+
17
+ with open(gt_pkl_path, 'rb') as file:
18
+ gt_data = pickle.load(file)
19
+
20
+ new_data = {"metainfo": gt_data["metainfo"]}
21
+ data_list = []
22
+
23
+ picked_scenes = [scene for scene in os.listdir(pred_path)]
24
+ num = 0
25
+ for scene in tqdm(gt_data['data_list'][:10]):
26
+ scene_name = scene['lidar_points']['lidar_path']
27
+ if scene_name not in picked_scenes:
28
+ print(f"Scene {scene_name} not found in {pred_path}")
29
+ continue
30
+ num += 1
31
+ tmp_scene = deepcopy(scene)
32
+ data_list.append(tmp_scene)
33
+ print(f"Number of scenes: {num}")
34
+ new_data['data_list'] = data_list
35
+ with open(out_pkl_path, 'wb') as f:
36
+ pickle.dump(new_data, f)
MaskClustering/build_pkl.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+ from tqdm.auto import tqdm
4
+ from copy import deepcopy
5
+
6
+
7
+
8
+ if __name__ == "__main__":
9
+ pred_path = \
10
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/data/prediction/arkit_gt_train"
11
+ out_pkl_path = \
12
+ "arkit_gt_train.pkl"
13
+ gt_pkl_path = \
14
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/arkitscenes/arkitscenes_offline_infos_train.pkl"
15
+
16
+
17
+ with open(gt_pkl_path, 'rb') as file:
18
+ gt_data = pickle.load(file)
19
+
20
+ new_data = {"metainfo": gt_data["metainfo"]}
21
+ data_list = []
22
+
23
+ picked_scenes = [scene[:-4] for scene in os.listdir(pred_path)]
24
+ num = 0
25
+ for scene in tqdm(gt_data['data_list']):
26
+ scene_name = scene['lidar_points']['lidar_path'].split("_")[0]
27
+ if scene_name not in picked_scenes:
28
+ print(f"Scene {scene_name} not found in {pred_path}")
29
+ continue
30
+ num += 1
31
+ tmp_scene = deepcopy(scene)
32
+ data_list.append(tmp_scene)
33
+ print(f"Number of scenes: {num}")
34
+ new_data['data_list'] = data_list
35
+ with open(out_pkl_path, 'wb') as f:
36
+ pickle.dump(new_data, f)
MaskClustering/cluster_masks.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import os
3
+ import cv2
4
+ from pathlib import Path
5
+ import trimesh as tm
6
+ from sklearn.neighbors import KDTree
7
+ from tqdm import tqdm
8
+ from tqdm.contrib.concurrent import thread_map
9
+ from sklearn.cluster import DBSCAN
10
+
11
+
12
+ def load_scan(pcd_path):
13
+ pcd_data = np.fromfile(pcd_path, dtype=np.float32).reshape(-1, 6)[:, :3]
14
+ return pcd_data
15
+
16
+ def process_scene(data):
17
+ scene_id, exp_name = data
18
+ pred_path = Path(f"data/prediction/scannet/click_sam/{scene_id}.npz")
19
+ out_path = Path(f"data/prediction/scannet/{exp_name}/{scene_id}.npz")
20
+ base_path = Path(f"/home/jovyan/users/lemeshko/scripts/gsam_result/yolo/{scene_id}")
21
+ source_path = Path(f"/home/jovyan/users/kolodiazhnyi/data/scannet/posed_images/{scene_id}")
22
+ scan_path = Path(f"/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannet200/points/{scene_id}.bin")
23
+ info_path = base_path / "infos.npy"
24
+
25
+ # if out_path.exists():
26
+ # return
27
+ vertices = load_scan(scan_path)
28
+ info_data = np.load(info_path, allow_pickle=True).item()
29
+
30
+ base_data = np.load(pred_path, allow_pickle=True)
31
+
32
+ total_points_masks = base_data['pred_masks'].T
33
+
34
+ for i, mask in enumerate(total_points_masks):
35
+ mask = mask.astype(bool)
36
+ points = vertices[mask]
37
+ db = DBSCAN(eps=0.3, min_samples=10)
38
+ if len(points) == 0:
39
+ continue
40
+ labels = db.fit_predict(points)
41
+
42
+ # labels = db.labels_
43
+ n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
44
+ if (labels == -1).all():
45
+ continue
46
+ biggest_cluster_ind = np.argmax(np.unique(labels[labels != -1], return_counts=True)[1])
47
+ res_mask = (labels == biggest_cluster_ind) & (labels != -1)
48
+ # print(f"{labels.shape} -> {res_mask.sum()}")
49
+ new_mask = np.zeros_like(mask)
50
+ new_mask[mask] = res_mask
51
+ total_points_masks[i] = new_mask
52
+
53
+ new_data = {
54
+ k: v for k, v in base_data.items()
55
+ }
56
+ new_data['pred_masks'] = total_points_masks.T
57
+
58
+ out_path.parent.mkdir(parents=True, exist_ok=True)
59
+ # vs = []
60
+ # cs = []
61
+ # for i in range(new_data['pred_masks'].shape[1]):
62
+ # os.makedirs(f"pred_masks", exist_ok=True)
63
+ # v = vertices[new_data['pred_masks'][:, i]]
64
+ # c = np.random.rand(3)
65
+ # c = np.repeat(c[np.newaxis, :], len(v), axis=0)
66
+ # vs.append(v)
67
+ # cs.append(c)
68
+ # tm.PointCloud(np.concatenate(vs, axis=0), colors=np.concatenate(cs, axis=0)).export(f"pred_masks/{scene_id}_mask.ply")
69
+
70
+ print("uniques", np.unique(new_data['pred_masks'].sum(1)), [[k, v.shape] for k, v in new_data.items()])
71
+ np.savez(out_path, **new_data)
72
+
73
+
74
+
75
+ if __name__ == "__main__":
76
+ exp_name = "cluster_filtering_click_sam"
77
+ # scenes = np.loadtxt("/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/splits/scannet.txt", dtype=str)
78
+ scenes = ["scene0011_00"]
79
+ data = [(scene, exp_name) for scene in scenes]
80
+ total_points_masks = thread_map(process_scene, data, chunksize=20)
81
+
82
+
83
+
MaskClustering/configs/arkit_dust3r_posed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "arkit_dust3r_posed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/arkit_gt.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "arkit_gt",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/arkit_gt_train.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "arkit_gt_train",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/arkit_vggt.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "arkit_vggt",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/demo.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "demo",
8
+ "cropformer_path": "/raid/miyan/ckpt/Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/itw.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "itw",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/matterport3d.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "matterport3d",
8
+ "cropformer_path": "/raid/miyan/ckpt/Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 1
10
+ }
MaskClustering/configs/scannet.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_15.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_15",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_25.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_25",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_35.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_35",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_35_bulat.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_35_bulat",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_45.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_45",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_45_andrey.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_45_andrey",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_posed_45_bulat.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_posed_45_bulat",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_unposed_15.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_unposed_15",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_unposed_25.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_unposed_25",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_unposed_35.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_unposed_35",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannet_dust3r_unposed_45.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "scannet_dust3r_unposed_45",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/configs/scannetpp.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp",
8
+ "cropformer_path": "/raid/miyan/ckpt/Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_dust3r_filtered_depth.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_dust3r_filtered_depth",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_dust3r_posed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_dust3r_posed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_dust3r_unposed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_dust3r_unposed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_mapanything_posed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_mapanything_posed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_v2_dust3r_posed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_v2_dust3r_posed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/scannetpp_v2_dust3r_unposed.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.4,
3
+ "undersegment_filter_threshold": 0.2,
4
+ "view_consensus_threshold": 1,
5
+ "contained_threshold": 0.9,
6
+ "point_filter_threshold": 0.7,
7
+ "dataset": "scannetpp_v2_dust3r_unposed",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 2
10
+ }
MaskClustering/configs/wild.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mask_visible_threshold": 0.3,
3
+ "undersegment_filter_threshold": 0.3,
4
+ "view_consensus_threshold": 0.9,
5
+ "contained_threshold": 0.8,
6
+ "point_filter_threshold": 0.5,
7
+ "dataset": "wild",
8
+ "cropformer_path": "Mask2Former_hornet_3x_576d0b.pth",
9
+ "step": 10
10
+ }
MaskClustering/dataset/demo.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import open3d as o3d
2
+ import numpy as np
3
+ import os
4
+ import cv2
5
+ from evaluation.constants import SCANNET_LABELS, SCANNET_IDS
6
+
7
+ class DemoDataset:
8
+
9
+ def __init__(self, seq_name) -> None:
10
+ self.seq_name = seq_name
11
+ self.root = f'./data/demo/{seq_name}'
12
+ self.rgb_dir = f'{self.root}/color_640'
13
+ self.depth_dir = f'{self.root}/depth'
14
+ self.segmentation_dir = f'{self.root}/output/mask'
15
+ self.object_dict_dir = f'{self.root}/output/object'
16
+ self.point_cloud_path = f'{self.root}/{seq_name}_vh_clean_2.ply'
17
+ self.mesh_path = self.point_cloud_path
18
+ self.extrinsics_dir = f'{self.root}/pose'
19
+
20
+ self.depth_scale = 1000.0
21
+ self.image_size = (640, 480)
22
+
23
+
24
+ def get_frame_list(self, stride):
25
+ image_list = os.listdir(self.rgb_dir)
26
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
27
+
28
+ end = int(image_list[-1].split('.')[0]) + 1
29
+ frame_id_list = np.arange(0, end, stride)
30
+ return list(frame_id_list)
31
+
32
+
33
+ def get_intrinsics(self, frame_id):
34
+ intrinsic_path = f'{self.root}/intrinsic_640.txt'
35
+ intrinsics = np.loadtxt(intrinsic_path)
36
+
37
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
38
+ intrinisc_cam_parameters.set_intrinsics(640, 480, intrinsics[0, 0], intrinsics[1, 1], intrinsics[0, 2], intrinsics[1, 2])
39
+ return intrinisc_cam_parameters
40
+
41
+
42
+ def get_extrinsic(self, frame_id):
43
+ pose_path = os.path.join(self.extrinsics_dir, str(frame_id) + '.txt')
44
+ pose = np.loadtxt(pose_path)
45
+ return pose
46
+
47
+
48
+ def get_depth(self, frame_id):
49
+ depth_path = os.path.join(self.depth_dir, str(frame_id) + '.png')
50
+ depth = cv2.imread(depth_path, -1)
51
+ depth = depth / self.depth_scale
52
+ depth = depth.astype(np.float32)
53
+ return depth
54
+
55
+
56
+ def get_rgb(self, frame_id, change_color=True):
57
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
58
+ rgb = cv2.imread(rgb_path)
59
+
60
+ if change_color:
61
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
62
+ return rgb
63
+
64
+
65
+ def get_segmentation(self, frame_id, align_with_depth=False):
66
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
67
+ if not os.path.exists(segmentation_path):
68
+ assert False, f"Segmentation not found: {segmentation_path}"
69
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
70
+ return segmentation
71
+
72
+
73
+ def get_frame_path(self, frame_id):
74
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
75
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
76
+ return rgb_path, segmentation_path
77
+
78
+
79
+ def get_label_features(self):
80
+ label_features_dict = np.load(f'data/text_features/scannet.npy', allow_pickle=True).item()
81
+ return label_features_dict
82
+
83
+
84
+ def get_scene_points(self):
85
+ mesh = o3d.io.read_point_cloud(self.point_cloud_path)
86
+ vertices = np.asarray(mesh.points)
87
+ return vertices
88
+
89
+
90
+ def get_label_id(self):
91
+ self.class_id = SCANNET_IDS
92
+ self.class_label = SCANNET_LABELS
93
+
94
+ self.label2id = {}
95
+ self.id2label = {}
96
+ for label, id in zip(self.class_label, self.class_id):
97
+ self.label2id[label] = id
98
+ self.id2label[id] = label
99
+
100
+ return self.label2id, self.id2label
MaskClustering/dataset/matterport.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import open3d as o3d
2
+ import numpy as np
3
+ import os
4
+ import cv2
5
+ from evaluation.constants import MATTERPORT_LABELS, MATTERPORT_IDS
6
+
7
+ class MatterportDataset:
8
+ def __init__(self, seq_name) -> None:
9
+ self.seq_name = seq_name
10
+ self.root = f'./data/matterport3d/scans/{seq_name}/{seq_name}'
11
+ self.rgb_dir = f'{self.root}/undistorted_color_images'
12
+ self.depth_dir = f'{self.root}/undistorted_depth_images'
13
+ self.cam_param_dir = f'{self.root}/undistorted_camera_parameters/{seq_name}.conf'
14
+ self.point_cloud_path = f'{self.root}/house_segmentations/{seq_name}.ply'
15
+ self.mesh_path = self.point_cloud_path
16
+ self.rgb_names, self.depth_names, self.intrinsics, self.extrinsics = \
17
+ self._obtain_intr_extr()
18
+
19
+ # output
20
+ self.segmentation_dir = f'{self.root}/output/mask/'
21
+ self.object_dict_dir = f'{self.root}/output/object'
22
+
23
+ self.depth_scale = 4000.0 # (0.25mm per unit) 1u = 1/4000 m
24
+ self.image_size = (1280, 1024)
25
+
26
+
27
+ def get_frame_list(self, step):
28
+ image_list = [os.path.join(self.rgb_dir, rgb_name) for rgb_name in self.rgb_names]
29
+
30
+ end = len(image_list)
31
+ frame_id_list = np.arange(0, end, step)
32
+ return list(frame_id_list)
33
+
34
+
35
+ def _obtain_intr_extr(self):
36
+ '''Obtain the intrinsic and extrinsic parameters of Matterport3D.'''
37
+
38
+ with open(self.cam_param_dir, 'r') as file:
39
+ lines = file.readlines()
40
+
41
+ def remove_items(test_list, item):
42
+ return [i for i in test_list if i != item]
43
+
44
+ intrinsics = []
45
+ extrinsics = []
46
+ img_names = []
47
+ depth_names = []
48
+ for i, line in enumerate(lines):
49
+ line = line.strip()
50
+ if 'intrinsics_matrix' in line:
51
+ line = line.replace('intrinsics_matrix ', '')
52
+ line = line.split(' ')
53
+ line = remove_items(line, '')
54
+ if len(line) !=9:
55
+ print('[WARN] something wrong at {}'.format(i))
56
+ intrinsic = np.asarray(line).astype(float).reshape(3, 3)
57
+ intrinsics.extend([intrinsic, intrinsic, intrinsic, intrinsic, intrinsic, intrinsic])
58
+ elif 'scan' in line:
59
+ line = line.split(' ')
60
+ img_names.append(line[2])
61
+ depth_names.append(line[1])
62
+
63
+ line = remove_items(line, '')[3:]
64
+ if len(line) != 16:
65
+ print('[WARN] something wrong at {}'.format(i))
66
+ extrinsic = np.asarray(line).astype(float).reshape(4, 4)
67
+ extrinsic[:3, 1] *= -1.0 # gl2cv
68
+ extrinsic[:3, 2] *= -1.0
69
+ extrinsics.append(extrinsic)
70
+
71
+ intrinsics = np.stack(intrinsics, axis=0)
72
+ extrinsics = np.stack(extrinsics, axis=0)
73
+ img_names = np.asarray(img_names)
74
+
75
+ return img_names, depth_names, intrinsics, extrinsics
76
+
77
+
78
+ def get_intrinsics(self, frame_id):
79
+ K = self.intrinsics[frame_id]
80
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
81
+ intrinisc_cam_parameters.set_intrinsics(self.image_size[0], self.image_size[1], K[0, 0], K[1, 1], K[0, 2], K[1, 2])
82
+ return intrinisc_cam_parameters
83
+
84
+
85
+ def get_extrinsic(self, frame_id):
86
+ return self.extrinsics[frame_id]
87
+
88
+
89
+ def get_depth(self, frame_id):
90
+ depth_path = os.path.join(self.depth_dir, self.depth_names[frame_id])
91
+ depth = cv2.imread(depth_path, -1).astype(np.uint16)
92
+ depth = depth / self.depth_scale
93
+ depth = depth.astype(np.float32)
94
+ return depth
95
+
96
+
97
+ def get_rgb(self, frame_id, change_color=True):
98
+ rgb = cv2.imread(os.path.join(self.rgb_dir, self.rgb_names[frame_id]))
99
+ if change_color:
100
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
101
+ return rgb
102
+
103
+
104
+ def get_segmentation(self, frame_id, align_with_depth=False):
105
+ frame_name = self.rgb_names[frame_id][:-4]
106
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_name}.png')
107
+ if not os.path.exists(segmentation_path):
108
+ assert False, f"Segmentation not found: {segmentation_path}"
109
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
110
+ return segmentation
111
+
112
+
113
+ def get_frame_path(self, frame_id):
114
+ rgb_path = os.path.join(self.rgb_dir, self.rgb_names[frame_id])
115
+ frame_name = self.rgb_names[frame_id][:-4]
116
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_name}.png')
117
+ return rgb_path, segmentation_path
118
+
119
+
120
+ def get_label_features(self):
121
+ label_features_dict = np.load(f'data/text_features/matterport3d.npy', allow_pickle=True).item()
122
+ return label_features_dict
123
+
124
+
125
+ def get_scene_points(self):
126
+ mesh = o3d.io.read_point_cloud(self.point_cloud_path)
127
+ vertices = np.asarray(mesh.points)
128
+ return vertices
129
+
130
+
131
+ def get_label_id(self):
132
+ self.label2id = {}
133
+ self.id2label = {}
134
+ for label, id in zip(MATTERPORT_LABELS, MATTERPORT_IDS):
135
+ self.label2id[label] = id
136
+ self.id2label[id] = label
137
+ return self.label2id, self.id2label
MaskClustering/dataset/scannet.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import open3d as o3d
2
+ import numpy as np
3
+ import os
4
+ import cv2
5
+ from evaluation.constants import SCANNET_LABELS, SCANNET_IDS, SCANNET18_LABELS, SCANNET18_IDS, SCANNETPP84_IDS, SCANNETPP84_LABELS, SCANNET20_LABELS, SCANNET20_IDS, ARKIT_LABELS, ARKIT_IDS
6
+
7
+ class ScanNetDataset:
8
+
9
+ def __init__(self, seq_name, root='data/scannet', use_templates=False) -> None:
10
+ self.seq_name = seq_name
11
+ self.use_templates = use_templates
12
+ self.root = os.path.join(root, 'processed', seq_name)
13
+ self.rgb_dir = f'{self.root}/color'
14
+ self.depth_dir = f'{self.root}/depth'
15
+ self.segmentation_dir = f'{self.root}/output/mask'
16
+ self.object_dict_dir = f'{self.root}/output/object'
17
+ self.point_cloud_path = f'{self.root}/{seq_name}_vh_clean_2.ply'
18
+ self.mesh_path = self.point_cloud_path
19
+ self.extrinsics_dir = f'{self.root}/pose'
20
+ self.intrinsic_dir = f'{self.root}/intrinsic'
21
+ self.label_features_dict = None
22
+
23
+ self.depth_scale = 1000.0
24
+ self.image_size = self.get_image_size()
25
+ self.depth_size = self.get_depth_shape()
26
+
27
+
28
+ def get_frame_list(self, stride):
29
+ image_list = os.listdir(self.rgb_dir)
30
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
31
+
32
+ end = int(image_list[-1].split('.')[0]) + 1
33
+ frame_id_list = [int(a.split('.')[0]) for a in image_list]
34
+ return list(frame_id_list)
35
+
36
+ def get_image_size(self):
37
+ image_list = os.listdir(self.rgb_dir)
38
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
39
+ image_path = os.path.join(self.rgb_dir, image_list[0])
40
+ image = cv2.imread(image_path)
41
+ return image.shape[:2][::-1]
42
+
43
+ def get_depth_shape(self):
44
+ image_list = os.listdir(self.rgb_dir)
45
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
46
+ depth_path = os.path.join(self.depth_dir, f"{image_list[0].split('.')[0]}.png")
47
+ depth = cv2.imread(depth_path, -1)
48
+ return depth.shape[:2][::-1]
49
+
50
+ def get_intrinsics(self, frame_id):
51
+ intrinsic_path = f'{self.intrinsic_dir}/intrinsic_depth.txt'
52
+ intrinsics = np.loadtxt(intrinsic_path)
53
+
54
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
55
+ intrinisc_cam_parameters.set_intrinsics(self.image_size[0], self.image_size[1], intrinsics[0, 0], intrinsics[1, 1], intrinsics[0, 2], intrinsics[1, 2])
56
+ return intrinisc_cam_parameters
57
+
58
+
59
+ def get_extrinsic(self, frame_id):
60
+ pose_path = os.path.join(self.extrinsics_dir, str(frame_id) + '.txt')
61
+ pose = np.loadtxt(pose_path)
62
+ return pose
63
+
64
+
65
+ def get_depth(self, frame_id):
66
+ depth_path = os.path.join(self.depth_dir, str(frame_id) + '.png')
67
+ depth = cv2.imread(depth_path, -1)
68
+ depth = depth / self.depth_scale
69
+ depth = depth.astype(np.float32)
70
+ return depth
71
+
72
+
73
+ def get_rgb(self, frame_id, change_color=True):
74
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
75
+ rgb = cv2.imread(rgb_path)
76
+
77
+ if change_color:
78
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
79
+ return rgb
80
+
81
+
82
+ def get_segmentation(self, frame_id, align_with_depth=False):
83
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
84
+ if not os.path.exists(segmentation_path):
85
+ assert False, f"Segmentation not found: {segmentation_path}"
86
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
87
+ if align_with_depth:
88
+ segmentation = cv2.resize(segmentation, self.depth_size, interpolation=cv2.INTER_NEAREST)
89
+ return segmentation
90
+
91
+
92
+ def get_frame_path(self, frame_id):
93
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
94
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
95
+ return rgb_path, segmentation_path
96
+
97
+
98
+ def get_label_features(self):
99
+ if self.label_features_dict is None:
100
+ if self.use_templates:
101
+ label_features_dict = np.load(f'data/text_features/scannet_templates.npy', allow_pickle=True).item()
102
+ else:
103
+ label_features_dict = np.load(f'data/text_features/scannet.npy', allow_pickle=True).item()
104
+ self.label_features_dict = label_features_dict
105
+ return self.label_features_dict
106
+
107
+
108
+ def get_scene_points(self):
109
+ mesh = o3d.io.read_point_cloud(self.point_cloud_path)
110
+ vertices = np.asarray(mesh.points)
111
+ return vertices
112
+
113
+
114
+ def get_label_id(self):
115
+ self.class_id = SCANNET_IDS
116
+ self.class_label = SCANNET_LABELS
117
+
118
+ self.label2id = {}
119
+ self.id2label = {}
120
+ for label, id in zip(self.class_label, self.class_id):
121
+ self.label2id[label] = id
122
+ self.id2label[id] = label
123
+
124
+ return self.label2id, self.id2label
125
+
126
+
127
+ class ARKitDataset(ScanNetDataset):
128
+ def __init__(self, seq_name, root='data/arkit_dust3r_posed'):
129
+ super().__init__(seq_name, root)
130
+ self.image_size = self.get_image_size()
131
+
132
+ def get_image_size(self):
133
+ image_list = os.listdir(self.rgb_dir)
134
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
135
+ image_path = os.path.join(self.rgb_dir, image_list[0])
136
+ image = cv2.imread(image_path)
137
+ return image.shape[:2][::-1]
138
+
139
+ def get_frame_list(self, stride):
140
+ image_list = os.listdir(self.rgb_dir)
141
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
142
+
143
+ end = int(image_list[-1].split('.')[0]) + 1
144
+ frame_id_list = [a.split('.')[0] for a in image_list]
145
+ return list(frame_id_list)
146
+
147
+ def get_label_id(self):
148
+ self.class_id = ARKIT_IDS
149
+ self.class_label = ARKIT_LABELS
150
+
151
+ self.label2id = {}
152
+ self.id2label = {}
153
+ for label, id in zip(self.class_label, self.class_id):
154
+ self.label2id[label] = id
155
+ self.id2label[id] = label
156
+
157
+ return self.label2id, self.id2label
158
+
159
+ def get_label_features(self):
160
+ label_features_dict = np.load(f'data/text_features/arkit.npy', allow_pickle=True).item()
161
+ return label_features_dict
162
+
163
+ class ITWDataset(ARKitDataset):
164
+
165
+ def get_image_size(self):
166
+ image_list = os.listdir(self.rgb_dir)
167
+ image_list = sorted(image_list, key=lambda x: int(x.split('_')[0]))
168
+ image_path = os.path.join(self.rgb_dir, image_list[0])
169
+ image = cv2.imread(image_path)
170
+ return image.shape[:2][::-1]
171
+
172
+ def get_depth_shape(self):
173
+ image_list = os.listdir(self.rgb_dir)
174
+ image_list = sorted(image_list, key=lambda x: int(x.split('_')[0]))
175
+ depth_path = os.path.join(self.depth_dir, f"{image_list[0].split('.')[0]}.png")
176
+ depth = cv2.imread(depth_path, -1)
177
+ return depth.shape[:2][::-1]
178
+
179
+ def get_frame_list(self, stride):
180
+ image_list = os.listdir(self.rgb_dir)
181
+ image_list = sorted(image_list, key=lambda x: int(x.split('_')[0]))
182
+
183
+ frame_id_list = [a.split('.')[0] for a in image_list]
184
+ return list(frame_id_list)
185
+
186
+ def get_label_features(self):
187
+ label_features_dict = np.load(f'{self.root}/text_features.npy', allow_pickle=True).item()
188
+ return label_features_dict
189
+
190
+ def get_label_id(self):
191
+ text_features = self.get_label_features()
192
+
193
+ self.class_label = list(text_features.keys())
194
+ self.class_id = list(range(len(self.class_label)))
195
+
196
+ self.label2id = {}
197
+ self.id2label = {}
198
+ for label, id in zip(self.class_label, self.class_id):
199
+ self.label2id[label] = id
200
+ self.id2label[id] = label
201
+
202
+ return self.label2id, self.id2label
203
+
204
+ class WildDataset(ARKitDataset):
205
+ def __init__(self, seq_name, root):
206
+ self.root = os.path.join(root, seq_name)
207
+ self.rgb_dir = f'{self.root}/images'
208
+ self.depth_dir = f'{self.root}/depth'
209
+ self.segmentation_dir = f'{self.root}/output/mask'
210
+ self.object_dict_dir = f'{self.root}/output/object'
211
+ self.point_cloud_path = f'{self.root}/point_cloud.ply'
212
+ self.mesh_path = self.point_cloud_path
213
+ self.extrinsics_dir = f'{self.root}/pose'
214
+ self.intrinsic_dir = f'{self.root}/intrinsic'
215
+ self.label_features_dict = None
216
+
217
+ self.depth_scale = 1000.0
218
+ self.image_size = self.get_depth_shape()
219
+ self.depth_size = self.get_depth_shape()
220
+
221
+ def get_label_features(self):
222
+ label_features_dict = np.load(f'{self.root}/text_features.npy', allow_pickle=True).item()
223
+ return label_features_dict
224
+
225
+ def get_segmentation(self, frame_id, align_with_depth=False):
226
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
227
+ if not os.path.exists(segmentation_path):
228
+ assert False, f"Segmentation not found: {segmentation_path}"
229
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
230
+ segmentation = cv2.resize(segmentation, self.depth_size, interpolation=cv2.INTER_NEAREST)
231
+ return segmentation
232
+ def get_label_id(self):
233
+ text_features = self.get_label_features()
234
+
235
+ self.class_label = list(text_features.keys())
236
+ self.class_id = list(range(len(self.class_label)))
237
+
238
+ self.label2id = {}
239
+ self.id2label = {}
240
+ for label, id in zip(self.class_label, self.class_id):
241
+ self.label2id[label] = id
242
+ self.id2label[id] = label
243
+
244
+ return self.label2id, self.id2label
245
+
246
+
247
+ class ScannetPP2Dataset(ScanNetDataset):
248
+ def __init__(self, seq_name, root='data/scannetpp_dust3r_posed'):
249
+ super().__init__(seq_name, root)
250
+ self.image_size = self.get_image_size()
251
+ self.depth_size = self.get_depth_shape()
252
+
253
+ self.point_cloud_path = f'{self.root}/{seq_name}.ply'
254
+
255
+ def get_image_size(self):
256
+ image_list = os.listdir(self.rgb_dir)
257
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0].split('_')[1]))
258
+ image_path = os.path.join(self.rgb_dir, image_list[0])
259
+ image = cv2.imread(image_path)
260
+ return image.shape[:2][::-1]
261
+
262
+ def get_depth_shape(self):
263
+
264
+ image_list = os.listdir(self.rgb_dir)
265
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0].split('_')[1]))
266
+ depth_path = os.path.join(self.depth_dir, f"{image_list[0].split('.')[0]}.png")
267
+ depth = cv2.imread(depth_path, -1)
268
+ return depth.shape[:2][::-1]
269
+
270
+ def get_frame_list(self, stride):
271
+ image_list = os.listdir(self.rgb_dir)
272
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0].split('_')[1]))
273
+
274
+ frame_id_list = [a.split('.')[0] for a in image_list]
275
+ return list(frame_id_list)
276
+
277
+ def get_segmentation(self, frame_id, align_with_depth=False):
278
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
279
+ if not os.path.exists(segmentation_path):
280
+ assert False, f"Segmentation not found: {segmentation_path}"
281
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
282
+ segmentation = cv2.resize(segmentation, self.depth_size, interpolation=cv2.INTER_NEAREST)
283
+ return segmentation
284
+
285
+
286
+ def get_label_id(self):
287
+ self.class_id = SCANNETPP84_IDS
288
+ self.class_label = SCANNETPP84_LABELS
289
+
290
+ self.label2id = {}
291
+ self.id2label = {}
292
+ for label, id in zip(self.class_label, self.class_id):
293
+ self.label2id[label] = id
294
+ self.id2label[id] = label
295
+
296
+ return self.label2id, self.id2label
297
+
298
+ def get_label_features(self):
299
+ label_features_dict = np.load(f'data/text_features/scannetpp84.npy', allow_pickle=True).item()
300
+ return label_features_dict
301
+
302
+ def get_depth(self, frame_id):
303
+ depth_path = os.path.join(self.depth_dir, str(frame_id) + '.png')
304
+ depth = cv2.imread(depth_path, -1)
305
+ depth = depth / self.depth_scale
306
+ depth = depth.astype(np.float32)
307
+ return depth
308
+
309
+
310
+ def get_intrinsics(self, frame_id):
311
+ intrinsic_path = f'{self.intrinsic_dir}/intrinsic_depth.txt'
312
+ intrinsics = np.loadtxt(intrinsic_path)
313
+
314
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
315
+ intrinisc_cam_parameters.set_intrinsics(self.image_size[0], self.image_size[1], intrinsics[0, 0], intrinsics[1, 1], intrinsics[0, 2], intrinsics[1, 2])
316
+ return intrinisc_cam_parameters
317
+
318
+
319
+ class ScanNet18Dataset:
320
+
321
+ def __init__(self, seq_name, root='data/scannet') -> None:
322
+ self.seq_name = seq_name
323
+ self.root = os.path.join(root, 'processed', seq_name)
324
+ self.rgb_dir = f'{self.root}/color'
325
+ self.depth_dir = f'{self.root}/depth'
326
+ self.segmentation_dir = f'{self.root}/output/mask'
327
+ self.object_dict_dir = f'{self.root}/output/object'
328
+ self.point_cloud_path = f'{self.root}/{seq_name}.ply'
329
+ self.mesh_path = self.point_cloud_path
330
+ self.extrinsics_dir = f'{self.root}/pose'
331
+ self.intrinsic_dir = f'{self.root}/intrinsic'
332
+
333
+ self.depth_scale = 1000.0
334
+ self.image_size = self.get_image_size()
335
+ self.depth_size = self.get_depth_shape()
336
+
337
+
338
+ def get_frame_list(self, stride):
339
+ image_list = os.listdir(self.rgb_dir)
340
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
341
+
342
+ end = int(image_list[-1].split('.')[0]) + 1
343
+ frame_id_list = [a.split('.')[0] for a in image_list]
344
+ return list(frame_id_list)
345
+
346
+ def get_image_size(self):
347
+ image_list = os.listdir(self.rgb_dir)
348
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
349
+ image_path = os.path.join(self.rgb_dir, image_list[0])
350
+ image = cv2.imread(image_path)
351
+ return image.shape[:2][::-1]
352
+
353
+ def get_depth_shape(self):
354
+ image_list = os.listdir(self.rgb_dir)
355
+ image_list = sorted(image_list, key=lambda x: int(x.split('.')[0]))
356
+ depth_path = os.path.join(self.depth_dir, f"{image_list[0].split('.')[0]}.png")
357
+ depth = cv2.imread(depth_path, -1)
358
+ return depth.shape[:2][::-1]
359
+
360
+ def get_intrinsics(self, frame_id):
361
+ intrinsic_path = f'{self.intrinsic_dir}/intrinsic_depth.txt'
362
+ intrinsics = np.loadtxt(intrinsic_path)
363
+
364
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
365
+ intrinisc_cam_parameters.set_intrinsics(self.image_size[0], self.image_size[1], intrinsics[0, 0], intrinsics[1, 1], intrinsics[0, 2], intrinsics[1, 2])
366
+ return intrinisc_cam_parameters
367
+
368
+
369
+ def get_extrinsic(self, frame_id):
370
+ pose_path = os.path.join(self.extrinsics_dir, str(frame_id) + '.txt')
371
+ pose = np.loadtxt(pose_path)
372
+ return pose
373
+
374
+
375
+ def get_depth(self, frame_id):
376
+ depth_path = os.path.join(self.depth_dir, str(frame_id) + '.png')
377
+ depth = cv2.imread(depth_path, -1)
378
+ depth = depth / self.depth_scale
379
+ depth = depth.astype(np.float32)
380
+ return depth
381
+
382
+
383
+ def get_rgb(self, frame_id, change_color=True):
384
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
385
+ rgb = cv2.imread(rgb_path)
386
+
387
+ if change_color:
388
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
389
+ return rgb
390
+
391
+
392
+ def get_segmentation(self, frame_id, align_with_depth=False):
393
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
394
+ if not os.path.exists(segmentation_path):
395
+ assert False, f"Segmentation not found: {segmentation_path}"
396
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
397
+ segmentation = cv2.resize(segmentation, self.depth_size, interpolation=cv2.INTER_NEAREST)
398
+ return segmentation
399
+
400
+
401
+ def get_frame_path(self, frame_id):
402
+ rgb_path = os.path.join(self.rgb_dir, str(frame_id) + '.jpg')
403
+ segmentation_path = os.path.join(self.segmentation_dir, f'{frame_id}.png')
404
+ return rgb_path, segmentation_path
405
+
406
+
407
+ def get_label_features(self):
408
+ label_features_dict = np.load(f'data/text_features/scannet18.npy', allow_pickle=True).item()
409
+ return label_features_dict
410
+
411
+
412
+ def get_scene_points(self):
413
+ mesh = o3d.io.read_point_cloud(self.point_cloud_path)
414
+ vertices = np.asarray(mesh.points)
415
+ return vertices
416
+
417
+
418
+ def get_label_id(self):
419
+ self.class_id = SCANNET18_IDS
420
+ self.class_label = SCANNET18_LABELS
421
+
422
+ self.label2id = {}
423
+ self.id2label = {}
424
+ for label, id in zip(self.class_label, self.class_id):
425
+ self.label2id[label] = id
426
+ self.id2label[id] = label
427
+
428
+ return self.label2id, self.id2label
429
+
430
+
431
+ class ScanNet20Dataset(ScanNet18Dataset):
432
+
433
+ def __init__(self, *args, **kwargs) -> None:
434
+ super().__init__(*args, **kwargs)
435
+ self.point_cloud_path = f'{self.root}/{self.seq_name}_vh_clean_2.ply'
436
+
437
+ def get_label_features(self):
438
+ label_features_dict = np.load(f'/home/jovyan/users/lemeshko/Indoor/MaskClustering/data/text_features/scannet20.npy', allow_pickle=True).item()
439
+ return label_features_dict
440
+
441
+
442
+ def get_label_id(self):
443
+ self.class_id = SCANNET20_IDS
444
+ self.class_label = SCANNET20_LABELS
445
+
446
+ self.label2id = {}
447
+ self.id2label = {}
448
+ for label, id in zip(self.class_label, self.class_id):
449
+ self.label2id[label] = id
450
+ self.id2label[id] = label
451
+
452
+ return self.label2id, self.id2label
MaskClustering/dataset/scannetpp.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import open3d as o3d
2
+ import numpy as np
3
+ import os
4
+ import cv2
5
+ import collections
6
+ from evaluation.constants import SCANNETPP_LABELS, SCANNETPP_IDS
7
+ import torch
8
+
9
+ BaseImage = collections.namedtuple(
10
+ "Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"])
11
+ BaseCamera = collections.namedtuple(
12
+ "Camera", ["id", "model", "width", "height", "params"])
13
+
14
+
15
+ def qvec2rotmat(qvec):
16
+ return np.array([
17
+ [1 - 2 * qvec[2]**2 - 2 * qvec[3]**2,
18
+ 2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],
19
+ 2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2]],
20
+ [2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],
21
+ 1 - 2 * qvec[1]**2 - 2 * qvec[3]**2,
22
+ 2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1]],
23
+ [2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],
24
+ 2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],
25
+ 1 - 2 * qvec[1]**2 - 2 * qvec[2]**2]])
26
+
27
+
28
+ class Image(BaseImage):
29
+ def qvec2rotmat(self):
30
+ return qvec2rotmat(self.qvec)
31
+
32
+ @property
33
+ def world_to_camera(self) -> np.ndarray:
34
+ R = qvec2rotmat(self.qvec)
35
+ t = self.tvec
36
+ world2cam = np.eye(4)
37
+ world2cam[:3, :3] = R
38
+ world2cam[:3, 3] = t
39
+ return world2cam
40
+
41
+
42
+ class Camera(BaseCamera):
43
+ @property
44
+ def K(self):
45
+ K = np.eye(3)
46
+ if self.model == "SIMPLE_PINHOLE" or self.model == "SIMPLE_RADIAL" or self.model == "RADIAL" or self.model == "SIMPLE_RADIAL_FISHEYE" or self.model == "RADIAL_FISHEYE":
47
+ K[0, 0] = self.params[0]
48
+ K[1, 1] = self.params[0]
49
+ K[0, 2] = self.params[1]
50
+ K[1, 2] = self.params[2]
51
+ elif self.model == "PINHOLE" or self.model == "OPENCV" or self.model == "OPENCV_FISHEYE" or self.model == "FULL_OPENCV" or self.model == "FOV" or self.model == "THIN_PRISM_FISHEYE":
52
+ K[0, 0] = self.params[0]
53
+ K[1, 1] = self.params[1]
54
+ K[0, 2] = self.params[2]
55
+ K[1, 2] = self.params[3]
56
+ else:
57
+ raise NotImplementedError
58
+ return K
59
+
60
+
61
+ def read_images_text(path):
62
+ images = {}
63
+ with open(path, "r") as fid:
64
+ while True:
65
+ line = fid.readline()
66
+ if not line:
67
+ break
68
+ line = line.strip()
69
+ if len(line) > 0 and line[0] != "#":
70
+ elems = line.split()
71
+ image_id = int(elems[0])
72
+ qvec = np.array(tuple(map(float, elems[1:5])))
73
+ tvec = np.array(tuple(map(float, elems[5:8])))
74
+ camera_id = int(elems[8])
75
+ image_name = elems[9]
76
+ elems = fid.readline().split()
77
+ xys = np.column_stack([tuple(map(float, elems[0::3])),
78
+ tuple(map(float, elems[1::3]))])
79
+ point3D_ids = np.array(tuple(map(int, elems[2::3])))
80
+ images[image_id] = Image(
81
+ id=image_id, qvec=qvec, tvec=tvec,
82
+ camera_id=camera_id, name=image_name,
83
+ xys=xys, point3D_ids=point3D_ids)
84
+ return images
85
+
86
+
87
+ def read_cameras_text(path):
88
+ """
89
+ see: src/base/reconstruction.cc
90
+ void Reconstruction::WriteCamerasText(const std::string& path)
91
+ void Reconstruction::ReadCamerasText(const std::string& path)
92
+ """
93
+ cameras = {}
94
+ with open(path, "r") as fid:
95
+ while True:
96
+ line = fid.readline()
97
+ if not line:
98
+ break
99
+ line = line.strip()
100
+ if len(line) > 0 and line[0] != "#":
101
+ elems = line.split()
102
+ camera_id = int(elems[0])
103
+ model = elems[1]
104
+ width = int(elems[2])
105
+ height = int(elems[3])
106
+ params = np.array(tuple(map(float, elems[4:])))
107
+ cameras[camera_id] = Camera(id=camera_id, model=model,
108
+ width=width, height=height,
109
+ params=params)
110
+ return cameras
111
+
112
+
113
+ class ScanNetPPDataset:
114
+
115
+ def __init__(self, seq_name) -> None:
116
+ self.seq_name = seq_name
117
+ self.root = f'./data/scannetpp/data/{seq_name}'
118
+ self.rgb_dir = f'{self.root}/iphone/rgb'
119
+ self.depth_dir = f'{self.root}/iphone/render_depth'
120
+ self.segmentation_dir = f'{self.root}/output/mask'
121
+ self.object_dict_dir = f'{self.root}/output/object'
122
+ self.point_cloud_path = f'./data/scannetpp/pcld_0.25/{seq_name}.pth'
123
+ self.load_meta_data()
124
+
125
+ self.depth_scale = 1000.0
126
+ self.image_size = (1920, 1440)
127
+
128
+
129
+ def load_meta_data(self):
130
+ self.frame_id_list = []
131
+
132
+ cameras = read_cameras_text(os.path.join(self.root, 'iphone/colmap', "cameras.txt"))
133
+ images = read_images_text(os.path.join(self.root, 'iphone/colmap', "images.txt"))
134
+ camera = next(iter(cameras.values()))
135
+ fx, fy, cx, cy = camera.params[:4]
136
+ intrinsics = {}
137
+ extrinsics = {}
138
+
139
+ for _, image in (images.items()):
140
+ image_id = int(image.name.split('.')[0].split('_')[1])
141
+ self.frame_id_list.append(image_id)
142
+ world_to_camera = image.world_to_camera
143
+ extrinsics[image_id] = np.linalg.inv(world_to_camera)
144
+ intrinsics[image_id] = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
145
+
146
+ self.extrinsics = extrinsics
147
+ self.intrinsics = intrinsics
148
+
149
+
150
+ def get_frame_list(self, stride):
151
+ return self.frame_id_list[::stride]
152
+
153
+
154
+ def get_intrinsics(self, frame_id):
155
+ intrinsic_matrix = self.intrinsics[frame_id]
156
+
157
+ intrinisc_cam_parameters = o3d.camera.PinholeCameraIntrinsic()
158
+ intrinisc_cam_parameters.set_intrinsics(self.image_size[0], self.image_size[1], intrinsic_matrix[0, 0], intrinsic_matrix[1, 1], intrinsic_matrix[0, 2], intrinsic_matrix[1, 2])
159
+ return intrinisc_cam_parameters
160
+
161
+
162
+ def get_extrinsic(self, frame_id):
163
+ return self.extrinsics[frame_id]
164
+
165
+
166
+ def get_depth(self, frame_id):
167
+ depth_path = os.path.join(self.depth_dir, 'frame_%06d.png' % frame_id)
168
+ depth = cv2.imread(depth_path, -1)
169
+ depth = depth / self.depth_scale
170
+ depth = depth.astype(np.float32)
171
+ return depth
172
+
173
+
174
+ def get_rgb(self, frame_id, change_color=True):
175
+ rgb_path = os.path.join(self.rgb_dir, 'frame_%06d.jpg' % frame_id)
176
+ rgb = cv2.imread(rgb_path)
177
+ if change_color:
178
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
179
+ return rgb
180
+
181
+
182
+ def get_segmentation(self, frame_id, align_with_depth=False):
183
+ segmentation_path = os.path.join(self.segmentation_dir, 'frame_%06d.png' % frame_id)
184
+ if not os.path.exists(segmentation_path):
185
+ assert False, f"Segmentation not found: {segmentation_path}"
186
+ segmentation = cv2.imread(segmentation_path, cv2.IMREAD_UNCHANGED)
187
+ return segmentation
188
+
189
+
190
+ def get_frame_path(self, frame_id):
191
+ rgb_path = os.path.join(self.rgb_dir, 'frame_%06d.jpg' % frame_id)
192
+ segmentation_path = os.path.join(self.segmentation_dir, 'frame_%06d.png' % frame_id)
193
+ return rgb_path, segmentation_path
194
+
195
+
196
+ def get_label_features(self):
197
+ label_features_dict = np.load(f'data/text_features/scannetpp.npy', allow_pickle=True).item()
198
+ return label_features_dict
199
+
200
+
201
+ def get_scene_points(self):
202
+ data = torch.load(self.point_cloud_path)
203
+ points = np.asarray(data['sampled_coords'])
204
+ return points
205
+
206
+
207
+ def get_label_id(self):
208
+ self.class_id = SCANNETPP_IDS
209
+ self.class_label = SCANNETPP_LABELS
210
+
211
+ self.label2id = {}
212
+ self.id2label = {}
213
+ for label, id in zip(self.class_label, self.class_id):
214
+ self.label2id[label] = id
215
+ self.id2label[id] = label
216
+
217
+ return self.label2id, self.id2label
MaskClustering/dense_masks.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import os
3
+ import cv2
4
+ from pathlib import Path
5
+ import trimesh as tm
6
+ from sklearn.neighbors import KDTree
7
+ from tqdm import tqdm
8
+ from tqdm.contrib.concurrent import thread_map
9
+ from sklearn.cluster import DBSCAN
10
+
11
+
12
+ def load_scan(pcd_path):
13
+ pcd_data = np.fromfile(pcd_path, dtype=np.float32).reshape(-1, 6)[:, :3]
14
+ return pcd_data
15
+
16
+ def process_scene(data):
17
+ scene_id, exp_name = data
18
+ pred_path = Path(f"data/prediction/scannet/baseline_scannet200/{scene_id}.npz")
19
+ out_path = Path(f"data/prediction/scannet/{exp_name}/{scene_id}.npz")
20
+ base_path = Path(f"/home/jovyan/users/lemeshko/scripts/gsam_result/yolo/{scene_id}")
21
+ source_path = Path(f"/home/jovyan/users/kolodiazhnyi/data/scannet/posed_images/{scene_id}")
22
+ scan_path = Path(f"/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannet200/points/{scene_id}.bin")
23
+ info_path = base_path / "infos.npy"
24
+
25
+ # if out_path.exists():
26
+ # return
27
+ vertices = load_scan(scan_path)
28
+ kd = KDTree(vertices)
29
+ max_dist = 0.05
30
+
31
+ base_data = np.load(pred_path, allow_pickle=True)
32
+
33
+ total_points_masks = base_data['pred_masks'].T
34
+
35
+ for i, mask in enumerate(total_points_masks):
36
+ mask = mask.astype(bool)
37
+ points = vertices[mask]
38
+ dists, inds = kd.query(points, k=5)
39
+ dists = dists.flatten()
40
+ inds = inds.flatten()
41
+
42
+ dist_mask = dists < max_dist
43
+ inds = inds[dist_mask]
44
+ mask[inds] = True
45
+ total_points_masks[i] = mask
46
+
47
+ for i, mask in enumerate(total_points_masks):
48
+ mask = mask.astype(bool)
49
+ points = vertices[mask]
50
+ db = DBSCAN(eps=0.3, min_samples=10)
51
+ labels = db.fit_predict(points)
52
+
53
+ # labels = db.labels_
54
+ n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
55
+ biggest_cluster_ind = np.argmax(np.unique(labels, return_counts=True)[1])
56
+ res_mask = (labels == biggest_cluster_ind) & (labels != -1)
57
+ # print(f"{labels.shape} -> {res_mask.sum()}")
58
+ new_mask = np.zeros_like(mask)
59
+ new_mask[mask] = res_mask
60
+ total_points_masks[i] = new_mask
61
+
62
+ new_data = {
63
+ k: v for k, v in base_data.items()
64
+ }
65
+ new_data['pred_masks'] = total_points_masks.T
66
+
67
+ out_path.parent.mkdir(parents=True, exist_ok=True)
68
+ # vs = []
69
+ # cs = []
70
+ # for i in range(new_data['pred_masks'].shape[1]):
71
+ # os.makedirs(f"pred_masks", exist_ok=True)
72
+ # v = vertices[new_data['pred_masks'][:, i]]
73
+ # c = np.random.rand(3)
74
+ # c = np.repeat(c[np.newaxis, :], len(v), axis=0)
75
+ # vs.append(v)
76
+ # cs.append(c)
77
+ # tm.PointCloud(np.concatenate(vs, axis=0), colors=np.concatenate(cs, axis=0)).export(f"pred_masks/{scene_id}_mask.ply")
78
+
79
+ print("uniques", np.unique(new_data['pred_masks'].sum(1)), [[k, v.shape] for k, v in new_data.items()])
80
+ np.savez(out_path, **new_data)
81
+
82
+
83
+
84
+ if __name__ == "__main__":
85
+ exp_name = "dense_masks_cluster_filtering"
86
+ scenes = np.loadtxt("/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/splits/scannet.txt", dtype=str)
87
+ data = [(scene, exp_name) for scene in scenes]
88
+ total_points_masks = thread_map(process_scene, data, chunksize=20)
89
+
90
+
91
+
MaskClustering/evaluation/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Инициализация модуля
MaskClustering/evaluation/constants.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MATTERPORT_LABELS = ('door', 'picture', 'window', 'chair', 'pillow', 'lamp',
2
+ 'cabinet', 'curtain', 'table', 'plant', 'mirror', 'towel', 'sink', 'shelves', 'sofa',
3
+ 'bed', 'night stand', 'toilet', 'column', 'banister', 'stairs', 'stool', 'vase',
4
+ 'television', 'pot', 'desk', 'box', 'coffee table', 'counter', 'bench', 'garbage bin',
5
+ 'fireplace', 'clothes', 'bathtub', 'book', 'air vent', 'faucet', 'photo', 'toilet paper',
6
+ 'fan', 'railing', 'sculpture', 'dresser', 'rug', 'ottoman', 'bottle', 'refridgerator',
7
+ 'bookshelf', 'wardrobe', 'pipe', 'monitor', 'stand', 'drawer', 'container', 'light switch',
8
+ 'purse', 'door way', 'basket', 'chandelier', 'oven', 'clock', 'stove', 'washing machine',
9
+ 'shower curtain', 'fire alarm', 'bin', 'chest', 'microwave', 'blinds', 'bowl', 'tissue box',
10
+ 'plate', 'tv stand', 'shoe', 'heater', 'headboard', 'bucket', 'candle', 'flower pot',
11
+ 'speaker', 'furniture', 'sign', 'air conditioner', 'fire extinguisher', 'curtain rod',
12
+ 'floor mat', 'printer', 'telephone', 'blanket', 'handle', 'shower head', 'soap', 'keyboard',
13
+ 'thermostat', 'radiator', 'kitchen island', 'paper towel', 'sheet', 'glass', 'dishwasher',
14
+ 'cup', 'ladder', 'garage door', 'hat', 'exit sign', 'piano', 'board', 'rope', 'ball',
15
+ 'excercise equipment', 'hanger', 'candlestick', 'light', 'scale', 'bag', 'laptop', 'treadmill',
16
+ 'guitar', 'display case', 'toilet paper holder', 'bar', 'tray', 'urn', 'decorative plate', 'pool table',
17
+ 'jacket', 'bottle of soap', 'water cooler', 'utensil', 'tea pot', 'stuffed animal', 'paper towel dispenser',
18
+ 'lamp shade', 'car', 'toilet brush', 'doll', 'drum', 'whiteboard', 'range hood', 'candelabra', 'toy',
19
+ 'foot rest', 'soap dish', 'placemat', 'cleaner', 'computer', 'knob', 'paper', 'projector', 'coat hanger',
20
+ 'case', 'pan', 'luggage', 'trinket', 'chimney', 'person', 'alarm')
21
+
22
+ MATTERPORT_IDS = [28, 64, 59, 5, 119, 144, 3, 89, 19, 82, 122, 135, 24, 42, 83, 157, 158, 124, 94, 453,
23
+ 215, 150, 78, 172, 16, 36, 26, 356, 7, 204, 12, 372, 141, 136, 1, 25, 9, 508, 139, 74, 497, 294,
24
+ 169, 130, 359, 2, 17, 88, 772, 41, 49, 50, 174, 140, 301, 181, 609, 39, 342, 238, 56, 242, 278,
25
+ 123, 338, 307, 344, 13, 80, 22, 138, 233, 291, 149, 111, 161, 427, 137, 146, 54, 524, 208, 79,
26
+ 10, 582, 143, 66, 32, 312, 758, 650, 133, 47, 110, 236, 456, 113, 559, 612, 8, 35, 48, 850, 193,
27
+ 86, 298, 408, 560, 60, 457, 211, 148, 62, 639, 55, 37, 458, 300, 540, 647, 51, 179, 151, 383, 515,
28
+ 324, 502, 509, 267, 678, 177, 14, 859, 530, 630, 99, 145, 45, 380, 605, 389, 163, 638, 154, 548,
29
+ 46, 652, 15, 90, 400, 851, 589, 783, 844, 702, 331, 525]
30
+
31
+ SCANNET_LABELS = ['chair', 'table', 'door', 'couch', 'cabinet', 'shelf', 'desk', 'office chair', 'bed', 'pillow', 'sink', 'picture', 'window', 'toilet', 'bookshelf', 'monitor', 'curtain', 'book', 'armchair', 'coffee table', 'box',
32
+ 'refrigerator', 'lamp', 'kitchen cabinet', 'towel', 'clothes', 'tv', 'nightstand', 'counter', 'dresser', 'stool', 'cushion', 'plant', 'ceiling', 'bathtub', 'end table', 'dining table', 'keyboard', 'bag', 'backpack', 'toilet paper',
33
+ 'printer', 'tv stand', 'whiteboard', 'blanket', 'shower curtain', 'trash can', 'closet', 'stairs', 'microwave', 'stove', 'shoe', 'computer tower', 'bottle', 'bin', 'ottoman', 'bench', 'board', 'washing machine', 'mirror', 'copier',
34
+ 'basket', 'sofa chair', 'file cabinet', 'fan', 'laptop', 'shower', 'paper', 'person', 'paper towel dispenser', 'oven', 'blinds', 'rack', 'plate', 'blackboard', 'piano', 'suitcase', 'rail', 'radiator', 'recycling bin', 'container',
35
+ 'wardrobe', 'soap dispenser', 'telephone', 'bucket', 'clock', 'stand', 'light', 'laundry basket', 'pipe', 'clothes dryer', 'guitar', 'toilet paper holder', 'seat', 'speaker', 'column', 'bicycle', 'ladder', 'bathroom stall', 'shower wall',
36
+ 'cup', 'jacket', 'storage bin', 'coffee maker', 'dishwasher', 'paper towel roll', 'machine', 'mat', 'windowsill', 'bar', 'toaster', 'bulletin board', 'ironing board', 'fireplace', 'soap dish', 'kitchen counter', 'doorframe',
37
+ 'toilet paper dispenser', 'mini fridge', 'fire extinguisher', 'ball', 'hat', 'shower curtain rod', 'water cooler', 'paper cutter', 'tray', 'shower door', 'pillar', 'ledge', 'toaster oven', 'mouse', 'toilet seat cover dispenser',
38
+ 'furniture', 'cart', 'storage container', 'scale', 'tissue box', 'light switch', 'crate', 'power outlet', 'decoration', 'sign', 'projector', 'closet door', 'vacuum cleaner', 'candle', 'plunger', 'stuffed animal', 'headphones', 'dish rack',
39
+ 'broom', 'guitar case', 'range hood', 'dustpan', 'hair dryer', 'water bottle', 'handicap bar', 'purse', 'vent', 'shower floor', 'water pitcher', 'mailbox', 'bowl', 'paper bag', 'alarm clock', 'music stand', 'projector screen', 'divider',
40
+ 'laundry detergent', 'bathroom counter', 'object', 'bathroom vanity', 'closet wall', 'laundry hamper', 'bathroom stall door', 'ceiling light', 'trash bin', 'dumbbell', 'stair rail', 'tube', 'bathroom cabinet', 'cd case', 'closet rod',
41
+ 'coffee kettle', 'structure', 'shower head', 'keyboard piano', 'case of water bottles', 'coat rack', 'storage organizer', 'folded chair', 'fire alarm', 'power strip', 'calendar', 'poster', 'potted plant', 'luggage', 'mattress']
42
+
43
+ SCANNET_IDS = [2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
44
+ 72, 73, 74, 75, 76, 77, 78, 79, 80, 82, 84, 86, 87, 88, 89, 90, 93, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 110, 112, 115, 116, 118, 120, 121, 122, 125, 128, 130, 131, 132, 134, 136, 138, 139, 140, 141, 145, 148, 154,
45
+ 155, 156, 157, 159, 161, 163, 165, 166, 168, 169, 170, 177, 180, 185, 188, 191, 193, 195, 202, 208, 213, 214, 221, 229, 230, 232, 233, 242, 250, 261, 264, 276, 283, 286, 300, 304, 312, 323, 325, 331, 342, 356, 370, 392, 395, 399, 408, 417,
46
+ 488, 540, 562, 570, 572, 581, 609, 748, 776, 1156, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191]
47
+
48
+
49
+ SCANNET18_LABELS = ['cabinet', 'bed', 'chair', 'sofa', 'table', 'door', 'window',
50
+ 'bookshelf', 'picture', 'counter', 'desk', 'curtain', 'refrigerator',
51
+ 'showercurtrain', 'toilet', 'sink', 'bathtub', 'garbagebin']
52
+
53
+ SCANNET18_IDS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 24, 28, 33, 34, 36, 39]
54
+
55
+ SCANNETPP_LABELS = ['door', 'table', 'cabinet', 'ceiling lamp', 'curtain', 'chair', 'blinds', 'storage cabinet', 'bookshelf', 'office chair', 'window', 'whiteboard', 'ceiling light', 'monitor', 'shelf', 'object', 'window frame', 'pipe', 'structure', 'box', 'heater', 'kitchen cabinet', 'storage rack', 'sofa', 'bed', 'shower wall', 'doorframe', 'door frame', 'roof', 'wardrobe', 'pillar', 'plant', 'blanket', 'machine', 'windowsill', 'linked retractable seats', 'window sill', 'cardboard box', 'tv', 'books', 'desk', 'computer tower', 'kitchen counter', 'trash can', 'trash bin', 'jacket', 'electrical duct', 'blackboard', 'cable tray', 'air duct', 'sink', 'carpet', 'bag', 'counter', 'refrigerator', 'picture', 'pillow', 'cupboard', 'window blind', 'towel', 'beam', 'office table', 'stool', 'suitcase', 'backpack', 'bathtub', 'rug', 'keyboard', 'rack', 'gym mat', 'toilet', 'suspended ceiling', 'shower floor', 'clothes', 'pipe storage rack', 'air conditioner', 'fume hood', 'printer', 'blind', 'poster', 'experiment bench', 'electrical control panel', 'shower curtain', 'windowframe', 'book', 'ceiling beam', 'painting', 'paper', 'ladder', 'laboratory bench', 'bench', 'milling machine', 'microwave', 'partition', 'board', 'office cabinet', 'rolling cart', 'laboratory cabinet', 'crate', 'raised floor', 'electrical panel', 'mattress', 'bottle', 'pedestal fan', 'sofa chair', 'headboard', 'fridge', 'bucket', 'kitchen unit', 'beanbag', 'oven', 'cushion', 'power socket', 'office desk', 'whiteboards', 'lab equipment', 'shoes', 'work bench', 'file cabinet', 'mirror', 'basket', 'beverage crate', 'washing machine', 'shoe rack', 'hydraulic press', 'photocopy machine', 'telephone', 'lab machine', 'sliding door', 'tv stand', 'objects', 'couch', 'coat', 'open cabinet', 'scientific equipment', 'coffee table', 'garage door', 'bin', 'radiator', 'standing lamp', 'stove', 'roller blinds', 'fume cupboard', 'pc', 'stairs', 'medical appliance', 'closet', 'trolley', 'file folder', 'projector', 'cloth', 'conference table', 'cardboard', 'blind rail', 'dishwasher', 'room divider', 'copier', 'ventilation pipe', 'bathroom cabinet', 'laptop', 'electrical box', 'arm chair', 'bar counter', 'stage', 'ceiling ventilator', 'lounge chair', 'plant pot', 'bathroom stall', 'pinboard', 'comforter', '3d printer', 'steel beam', 'projector screen', 'electric duct', 'cart', 'air pipe', 'training equipment', 'floor mounted air conditioner', 'tile wall', 'glass wall', 'exhaust fan', 'vacuum cleaner', 'laundry basket', 'nightstand', 'armchair', 'drying rack', 'indoor crane', 'storage trolley', 'dresser', 'l-shaped sofa', 'coat hanger', 'dining chair', 'office visitor chair', 'interactive board', 'hose', 'light switch', 'shower ceiling', 'coffee machine', 'cables', 'floor lamp', 'fan', 'wire tray', 'compressor', 'laboratory equipment', 'dining table', 'speaker', 'climbing wall', 'light', 'paper towel dispenser', 'coat rack', 'table lamp', 'frame', 'duvet', 'fire extinguisher', 'range hood', 'high table', 'backdrop', 'ventilation duct', 'seat', 'tablecloth', 'electrical cabinet', 'ping pong table', 'bathroom floor', 'ceiling pipe', 'bedside table', 'coffee maker', 'computer desk', 'urinal', 'loft bed', 'air vent', 'chairs', 'bedsheet', 'television', 'lamp', 'rolling chair', 'wall cabinet', 'book shelf', 'brick wall', 'treadmill', 'vent', 'shirt', 'canopy bed', 'clothes hanger', 'kettle', 'shoe', 'high stool', 'tripod', 'bar stool', 'exhaust duct', 'wooden plank', 'squat rack', 'cubicle door', 'folding screen', 'kitchen sink', 'container', 'bottles', 'ottoman', 'bicycle', 'staircase railing', 'overhead projector', 'surfboard', 'folder', 'power strip', 'high bench', 'wall beam', 'pallet cage', 'interactive whiteboard', 'floor sofa', 'duct', 'flat panel display', 'wooden frame', 'folding room divider', 'cable', 'mug', 'rolling table', 'locker', 'standing banner', 'decoration', 'clothes drying rack', 'foosball table', 'standing poster', 'bath tub', 'yoga mat', 'microscope', 'paper bag', 'mouse', 'umbrella', 'medical machine', 'smoke detector', 'cup', 'cutting board', 'console', 'drum', 'bathroom counter', 'toilet paper', 'robot car', 'exhaust pipe', 'bath cabinet', 'whiteboard stand', 'notice board', 'paper towel', 'crates', 'bed frame', 'bathroom mat', 'shower partition', 'cloth hangers', 'clothes cabinet', 'tv screen', 'babyfoot table', 'rolling curtain', 'coat stand', 'kitchen towel', 'plank', 'side table', 'storage shelf', 'mat', 'shower', 'white board', 'information board', 'backsplash', 'guitar', 'cloth rack', 'ceiling vent', 'partition wall', 'kitchen shelf', 'banner', 'file binder', 'cleaning trolley', 'racing simulator', 'workbench', 'pot', 'ac system', 'power panel', 'desk lamp', 'broom', 'cpu', 'fitted wardrobe', 'tote bag', 'plumbing pipe', 'slippers', 'blackboard frame', 'magazine', 'hose pipe', 'rolled paper', 'sweater', 'clock', 'tray', 'desk fan', 'vaccum cleaner', 'projection curtain', 'freezer display counter', 'pan', 'vase', 'glass', 'folders', 'tap', 'wall lamp', 'plate', 'laptop stand', 'small cabinet', 'file organizer', 'clothes dryer', 'wall painting', 'curtain rail', 'wheelchair', 'bottle crate', 'sheet', 'folding sofa', 'shower pan', 'plastic case', 'christmas tree', 'piano', 'ottoman chair', 'jar', 'foldable closet', 'notebook', 'calendar', 'janitor cart', 'storage box', 'rolling blinds', 'bathroom shelf', 'soap dispenser', 'binder', 'copy machine', 'rice cooker', 'gym mattress', 'car door', 'table football', 'bowl', 'light panel', 'tissue box', 'bedframe', 'wall hanging', 'jug', 'skylight', 'ceiling fan', 'dish rack', 'shelving cart', 'instant pot', 'whiteboard eraser', 'floor mat', 'socket', 'mini fridge', 'wall clock', 'boots', 'barbecue grill', 'paper shredder', 'file rack', 'floor scrubber', 'metal board', 'water heater', 'tool rack', 'recliner', 'barber chair', 'ventilator', 'trolley table', 'standing fan', 'water filter', 'shoes holder', 'vr setup', 'trashcan', 'bike', 'lab materials', 'wooden pallet', 'dustbin', 'curtain rod', 'reflection', 'toilet brush', 'exercise ball', 'air purifier', 'kitchen back splash', 'paper rack', 'toolbox', 'monitor cover', 'file', 'surfsuit', 'night stand', 'paper organizer', 'serving trolley', 'phone', 'canvas', 'camping bed', 'tower pc', 'cylinder', 'magazine stand', 'toy', 'slipper', 'air conditioning', 'hanger', 'vertical blinds', 'desk organizer', 'guitar bag', 'spray bottle', 'suit cover', 'toaster', 'spotlight', 'machine container', 'foot rest', 'shopping trolley', 'decoration piece', 'control panel', 'multifunction printer', 'jerry can', 'window head', 'cooker hood', 'basin', 'panel', 'papasan chair', 'tv mount', 'toilet seat', 'shopping bag', 'photocopier', 'tube', 'studio light', 'stuffed toy', 'cord cover', 'power cabinet', 'filer organizer', 'garage shelf', 'luggage', 'gym bag', 'exhaust hood', 'microwave oven', 'floor cushion', 'easy chair', 'bar table', 'shoe cabinet', 'paper tray', 'lab coat', 'toilet paper dispenser', 'kitchen storage rack', 'equipment', 'computer table', 'mouse pad', 'drawer', 'headphones', 'bathroom sink', 'outlet', 'toaster oven', 'tv table', 'bedside cabinet', 'rolling trolley', 'step stool', 'trousers', 'bathroom rack', 'shelf trolley', 'glass shelf', 'fabric', 'lab fridge', 'work station', 'barrel', 'mop', 'deck chair', 'bath counter', 'helmet', 'standing clothes hanger', 'garbage bin', 'study table', 'air fryer', 'plastic bag', 'oven range', 'headphone', 'kitchen counter top', 'clothes rack', 'wall unit', 'grab bar', 'flipchart', 'scarf', 'labcoat', 'hat', 'bedside lamp', 'sewing machine table', 'shower head', 'switchboard cabinet', 'flip paper', 'storage container', 'canister', 'wall board', 'shower rug', 'plastic box', 'stovetop', 'information stand', 'footstool', 'pack', 'push cart', 'table cloth', 'celing lamp', 'cupoard', 'jeans', 'smoke alarm', 'bath mat', 'softbox', 'whiteboard mount', 'paper cutter', 'cable raceway', 'water kettle', 'pelican case', 'towel rack', 'rolling shelf cart', 'built-in shelf', 'equipment cover', 'television stand', 'sheets', 'small dresser', 'light stand', 'beach umbrella', 'faucet', 'bagpack', 'dumbbell', 'water dispenser', 'medicine cabinet', 'tv console', 'mirror frame', 'chandelier', 'pen holder', 'messenger bag', 'ball', 'glass bottle', 'softbox light', 'gym ball', 'briefcase', 'plastic bottle', 'monitor stand', 'human skeleton', 'podium', 'wall strip', 'tablet', 'bedside shelf', 'headrail', 'sink counter', 'doormat', 'baseboard', 'bulletin board', 'electric hob', 'bean bag', 'high pressure cylinder', 'portable fan', 'flush button', 'wooden post', 'lectern', 'curtain frame', 'computer monitor', 'folding chair', 'tabletop', 'led ceiling fan', 'high chair', 'grill', 'metal rack', 'air conditioner tower', 'sliding door frame', 'cable rack', 'bench press', 'ironing board', 'wooden palette', 'kitchenware', 'blind rails', 'plastic container', 'weighing scale', 'headset', 'tree trunk', 'shower screen', 'wall shelf', 'watering can', 'tool box', 'bed sheet', 'glass pane', 'tower fan', 'switch', 'notice', 'sack', 'table mat', 'flower pot', 'dog bed', 'laundry hanger', 'mobile tv stand', 'file holder', 'floor couch', 'tv trolley', 'chopping board', 'centrifuge', 'tubelight', 'bedpost', 'step', 'center table', 'upholstered bench', 'sink pipe', 'door mat', 'storage bin', 'towel radiator', 'shower tray', 'electronic appliance', 'boiler', 'food container', 'cable pathway', 'carboard box', 'metal sheet', 'hand bag', 'sign', 'laundry rack', 'screen', 'cardbox', 'fireplace surround', 'boot', 'envelope', 'carton', 'tool organizer', 'paper roll', 'water bottle', 'shoe changing stool', 'balcony door', 'espresso machine', 'water pipe', 'recesssed shelf', 'drum set', 'skiboard', 'speaker stand', 'kitchen wall', 'suit', 'photo', 'globe', 'spice rack', 'delivery bag', 'router', 'rolling blind', 'easel', 'shower cubicle', 'dish drainer', 'doorway', 'folded table', 'pants', 'computer', 'stuffed animal', 'office chair', 'cable conduit', 'picture frame', 'shoe stool', 'recessed shelve', 'toilet paper holder', 'panelboard', 'stapler', 'skateboard', 'workshop tool', 'projector holder', 'flag', 'chemical canister', 'web cam', 'hoodie', 'towel heater', 'towel warmer', 'shower curtain rod', 'shower faucet', 'shower door', 'laboratory power supply', 'tool', 'ventilation', 'soap bottle', 'bathrobe', 'pictures board', 'cap', 'woofer', 'tshirt', 'rolling bag', 'shoe box', 'luggage bag', 'file storage', 'cat bed', 'stack of paper', 'surfing board', 'electric kettle', 'rolling stand', 'cover', 'main switchboard', 'pressure cooker', 'stepladder', 'countertop', 'flip flops', 'short table', 'sit-up pillow', 'duffel bag', 'shower seating', 'washbasin', 'teddy bear', 'stair', 'plate rack', 'ornament', 'jerrycan', 'filter jug', 't shirt', 'cooking pot', 'platform trolley', 'blinds rod', 'hand shower', 'power socket unit', 'sheep doll', 'laptop bag', 'game console', 'bottles case', 'lid', 'dumbbell case', 'rolled blanket', 'paper stapler', 'kitchen pot', 'charcoal bag', 'laundry hamper', 'rolled poster', 'bath towel', 'apron', 'dustpan', 'trash bag', 'document tray', 'camera', 'mirror cabinet', 'dish drying rack', 'gas tank', 'cable roller', 'case', 'ring light', 'hair dryer', 'gym plate', 'hand towel', 'sill', 'sidetable', 'vice', 'bench stool', 'billboard', 'rolling cabinet', 'shower sink', 'cloth piece', 'oscilloscope', 'magazine rack', 'wash basin', 'cable panel', 'photo frame', 'tv receiver', 'stand', 'milk jug', 'wooden board', 'bladeless fan', 'door frame', 'wall paper', 'scale', 'purse', 'electronic device', 'sofa cushion', 'sponge', 'dish washer', 'crate trolley', 'kitchen hood', 'laundry vent', 'medical stool', 'exhaustive fan', 'portable ladder', 'chemical container', 'toilet paper rolls', 'rag', 'blender', 'window pane', 'dog bowl', 'shopping basket', 'piano stool', 'electric box', 'wall calendar', 'paper holder', 'chemical bottle', 'sandals', 'foreman grill', 'guitar case', 'heater tube', 'running shoes', 'shower tap', 'cloth hanger', 'microphone', 'cabinet frame', 'decorative object', 'light fixture', 'ceiling lamp bar', 'paperbag', 'chemical barrel', 'wicker basket', 'exit sign', 'bottles rack', 'water jug', 'bottle carrier', 'laboratory pellet press', 'mini oven', 'shower arm', 'paper tube', 'suitcase stand', 'table fan', 'shelve', 'full-length mirror', 'wood piece', 'can', 'suit bag', 'water bubbler', 'first aid kit', 'kitchen robot', 'toilet flush button', 'pillow toy', 'plush doll', 'styrofoam box', 'document organizer', 'pet carrier', 'folding table', 'gloves', 'pitcher', 'cable spool', 'rolled cable', 'folding umbrella', 'robot vacuum cleaner', 'tower ventilator', 'brush', 'planter', 'baseball cap', 'gas cylinder', 'stereo', 'baby stroller', 'water bucket', 'rucksack', 'shower door frame', 'drone', 'kitchen cloth', 'hand soap dispenser', 'pegboard', 'alarm', 'emergency light', 'sign board', 'weight plate', 'rolled projection screen', 'laptop table', 'hole puncher', 'mixer', 'piano chair', 'paper bin', 'wooden stick', 'fireplace', 'rolled backdrop', 'mousepad', 'long pillow', 'bananas', 'column', 'cd player', 'eraser', 'laptop sleeve', 'hairdryer', 'seat cushion', 'plant pot mat', 'wastebin', 'wood panel', 'detergent bottle', 'safe box', 'pouch', 'blind rod', 'mop basin', 'plug', 'document holder', 'railing', 'plastic drum', 'cat tree', 'mirror light', 'kettlebell', 'chart', 'dust pan', 'sandal', 'first aid cabinet', 'bracket', 'wire', 'scooter', 'racing wheel', 'wine rack', 'belt', 'tissue dispenser stand', 'towel paper dispenser', 'air heater', 'plushie', 'knife set', 'iron', 'intercom', 'kitchen ceiling', 'package', 'toilet paper dispensor', 'sneakers', 'umbrella stand', 'egg carton', 'organizer', 'stick', 'shampoo bottle', 'cone', 'file tray', 'wooden plan', 'cooking pan', 'brief', 'paper towel package', 'glove dispenser', 'dispenser bottle', 'kitchen drawer', 'remote control', 'powerstrip', 'emergency shower', 'scanner', 'towel holder', 'vr headset', 'watering pot', 'soda machine', 'pole stand', 'roomba', 'rolling mat', 'fluorescent lamp', 'flower', 'pc tower', 'metal mount', 'oven gloves', 'soap', 'flush tank', 'notepad', 'pull up bar', 'loafers', 'water meter cover', 'plastic tray', 'webcam', 'barbell', 'tea pot', 'wall hanger', 'cabinet base panel', 'laptop case', 'paper towel holder', 'skeleton', 'socket extender', 'extension chord reel', 'wooden crate', 'guillotine paper cutter', 'sewing machine', 'model car', 'bed cover', 'storage', 'freezer', 'piano book', 'wifi router', 'overhead shower', 'chrismas tree', 'drill', 'clothes drying stand', 'dumbell', 'cabel', 'badminton racket', 'cool box', 'thermostat', 'dartboard', 'switchboard cover', 'candle', 'electric stove', 'paper ram', 'insulated can', 'prosthetic leg', 'desk power strip', 'closet rail', 'water pitcher', 'plate weights', 'extension cord', 'tea box', 'tissue', 'sauce pan', 'toothbrush', 'frying pan', 'package of paper', 'microphone stand', 'socket box', 'leather mattress', 'plastic can', 'garbage bin cover', 'plush toy', 'electric guitar', 'weight scale', 'fruit', 'tape dispenser', 'pallet', 'emergency kit', 'bathroom mirror', 'door lamp', 'power extension', 'electric circuit board', 'oven panel', 'electrical pipe', 'shade', 'photoframe', 'file stack', 'pizza box', 'tennis racket', 'wall hook', 'recycle bag', 'recessed shower shelve', 'wall mounted telephone', 'facsimile', 'kitchen roll', 'totebag', 'floor cleaner', 'body weight scale', 'stuffed animal door insulator', 'neck pillow', 'basketball', 'cable wheel', 'door vent', 'foot massager', 'pumper', 'hanging light fixture', 'interphone', 'paper towel roll', 'control unit', 'folder oragnizer', 'hanging frame', 'paper stack', 'door handle', 'mini shelf', 'beverage carton', 'wok pan', 'bedside counter', 'bar', 'pot lid', 'radio', 'paper box', 'stabilizer', 'headphone case', 'folded cardboard box', 'coaster', 'pen tray', 'marker', 'shower mat', 'rod', 'device', 'electric pot', 'mixer machine', 'barstool', 'bread toaster', 'electric mixer', 'wall cord cover', 'camera bag', 'charger', 'knife', 'metal frame', 'folded bag', 'conduit pipe', 'french press', 'cabinet side panel', 'cosmetic bag', 'surveillance camera', 'bathroom holder', 'footrest', 'glasses case', 'handbag', 'cooling pad', 'flip flop', 'game controller', 'packet of toilet paper', 'cable duct', 'toilet paper roll', 'monitor support', 'fire alarm', 'kitchen stove', 'key hanger', 'tub', 'network socket', 'ceilng light', 'christmas ornament', 'pepper mill', 'wall outlet', 'light cover', 'caution board', 'heels', 'package bag', 'blinds rail', 'candle holder', 'electric toothbrush', 'tool case', 'toilet flush', 'wooden brush', 'hand washing soap', 'hygiene product', 'water tap', 'cosmetic pouch', 'mount', 'knife holder', 'dustpan and brush', 'circular tray', 'shoe case', 'ar tag', 'flipflop', 'sculpture', 'recycle bin', 'kitchen utensil', 'multiplug', 'beaker stand', 'chessboard', 'pen', 'toothpaste', 'tv remote', 'floor wiper', 'wire hider', 'water meter', 'magazine holder', 'mailbox', 'paper file', 'wall coat hanger', 'utensil holder', 'detergent', 'safe', 'packet', 'dvd', 'dvd player', 'tupperware', 'electrical board', 'radiator pipe', 'plat', 'decorative mirror', 'telephone stand', 'water boiler', 'cutboard', 'hanging deer skull', 'file orginizer', 'satchel', 'head model', 'parcel', 'dish soap bottle', 'glass plate', 'pencil case', 'cleaning mop', 'mixer glass', 'joystick', 'vacuum flask', 'bag of oranges', 'pencil holder', 'pamphlet', 'rope', 'cd', 'knife stand', 'pencil cup', 'binding machine', 'grill pan', 'tape', 'cabinet top panel', 'cutti̇ng board', 'salad spinner', 'water filter jug', 'mop cloth', 'shower valve', 'laptop cover', 'running shoe', 'alligator clips', 'insulated coffee mug', 'pencil stand', 'action figure', 'desk light', 'midi controller', 'bathroom slippers', 'elephant decoration piece', 'switchboard', 'remote', 'arch folder', 'power cord', 'hot bag', 'pencils cup', 'knife block', 'thermos', 'power switch', 'toothbrush holder', 'cleaning liquid', 'power board', 'pull-up bar', 'sandwich maker', 'statue', 'brief case', 'airdyer', 'bike helmet', 'mirror lamp', 'tennis rackets', 'paint jar', 'citrus juicer', 'scissors', 'hanging hook', 'napkin', 'laundry detergent', 'disinfectant dispenser', 'cleaning brush', 'food', 'pan set', 'kitchen appliance', 'mop pad', 'dish soap', 'plunger', 'paper package', 'plastic mat', 'calculator', 'voltage stabilizer', 'whiteboard marker', 'wall coat rack', 'milk carton', 'cosmetic bottle', 'shower handle', 'tissue paper', 'shorts', 'game console controller', 'remote controller', 'teapot', 'drain', 'juice box', 'phone stand', 'soap container', 'flush', 'rubber water bag', 'shower gel', 'snack bag', 'watering bucket', 'first aid box', 'banana', 'paper notebook', 'hammer', 'kitchen glove', 'handle', 'personal hygiene product', 'duster', 'robot vaccuum cleaner', 'chain', 'soap dish', 'cereal box', 'door window', 'elephant toy', 'frisbee', 'cream tube', 'folder holder', 'internet socket', 'moka pot', 'stereo box', 'face mask', 'bunny chocolate', 'magazine collector', 'shower holder', 'toilet brush', 'oven glove', 'shower drain', 'penholder', 'switch board', 'coffee pot', 'marker eraser', 'squeegee', 'spray', 'game cd box', 'table clock', 'apple', 'spool', 'portable speaker', 'guitar pedal', 'bread', 'glass jar', 'paper puncher', 'dishwashing sponge', 'shower hose', 'surface cleaning liquid', 'wallet', 'circuit box', 'toiletry', 'hair brush', 'headphone bag', 'metal saw', 'power adapter', 'wlan router', 'plant pot coaster', 'oven mitt', 'hanging light', 'kitchen object', 'machine button', 'vanity light', 'flush plate', 'leaf fan', 'punching machine', 'window handle', 'wine bottle', 'tennis cap', 'chips can', 'food pot', 'hand washing soap dispenser', 'wall light', 'detergent bag', 'glasses cover', 'hangbag', 'bottle spray', 'kitchen tap', 'knob', 'figurine', 'hand vacuum', 'potted plant', 'marker storage', 'mixing bowl', 'power supply', 'intercom screen', 'coffee mug', 'drilling machine', 'electric socket', 'hand shower handle', 'hole punch', 'silicon gun', 'document holder', 'wooden chest', 'cheese', 'headphones case', 'measuring spoon', 'monitor light', 'ear muffs', 'night lamp', 'wall handle', 'intercom device', 'lanyard', 'rugby ball', 'shower loofah', 'cable socket', 'cleaning cloth', 'dish washer soap', 'power brick', 'coffee', 'monitor base', 'mushroom lamp', 'trivet', 'surface cleaner', 'toiletry bottle', 'paper note', 'strainer', 'colander', 'kitchen brush', 'multi socket', 'saucer', 'scissor', 'stamp', 'emergency button', 'toilet brush holder', 'coffee jar', 'deodorant', 'shower rod', 'wet tissue', 'tissue paper roll', 'funnel', 'oragnizer', 'toilet plunger', 'tumbler', 'door knob', 'coarser', 'postit note', 'punching tool', 'beverage can', 'dish', 'star', 'flask', 'in-table power socket', 'laptop charger', 'wall intercom', 'flour bag', 'blowtorch', 'cleaner', 'takeout box', 'hand soap', 'hard drive', 'monitor holder', 'soldering iron', 'control switch', 'drainage', 'box lid', 'playstation controller', 'shaving foam', 'guitar stand', 'toilet cleaner', 'co detector', 'covered power socket', 'spray can', 'tooth brush', 'phone tripod', 'bluetooth speaker', 'dish washing liquid', 'powerbank', 'dried plant', 'shampoo', 'sticky note', 'toilet seat brush', 'dishwashing soap', 'lan port', 'scrubber', 'cord', 'ashtray', 'handle bar', 'dongle', 'liquid soap', 'mobile phone', 'bunny decoration', 'palm rest', 'stream deck', 'blinds chain', 'dishwashing liquid', 'hammer holder', 'hand washing liquid', 'shower wiper', 'duct tape', 'valve', 'glove', 'stationery', 'ceiling speaker', 'holder', 'power plug', 'wall speaker', 'card', 'electrical tape', 'pot cover', 'razor', 'barcode scanner', 'lotion', 'grab rail', 'hairbrush', 'lint roller', 'post-it', 'dusting cloth', 'glue bottle', 'handwash', 'network outlet', 'card reader', 'kitchen light', 'pc charger', 'product dispenser bottle', 'hair dryer holder', 'screwdriver', 'shower squeegee', 'juice tetrapack', 'pliers', 'sunblock', 'bed sheet', 'hook', 'headphone holder', 'light bulb', 'ladle', 'bar soap', 'button', 'dish brush', 'toy car', 'note', 'post it', 'sponge cloth', 'wood stick', 'paper weight', 'product tube', 'cell phone', 'smartphone', 'comb', 'jump rope', 'lan', 'wireless charger', 'door stopper', 'cigarette packet', 'pencil', 'soap holder', 'electrical plug', 'toilet holder', 'mask', 'pocket calculator', 'cabbage', 'spoon', 'doorknob', 'fan switch', 'rubber duck', 'backdrop hook', 'table tennis racket', 'receipt', 'soap bar', 'tape roll', 'tooth paste', 'electrical adapter', 'probe', 'highlighter', 'correction fluid', 'dispenser', 'pi̇cture', 'door hinge', 'whiteboard marker holder', 'botttle', 'power outlet', 'towel hanger', 'whiteboard duster', 'magnet', 'sticker', 'nose spray', 'vertical blind control', 'razor blade', 'post it note', 'wireless headphones', 'cabinet top', 'cream bottle', 'datashow socket', 'earbuds', 'cabinet door', 'chair cushion', 'cosmetic tube']
56
+
57
+ SCANNETPP_IDS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 575, 576, 577, 578, 580, 581, 583, 584, 585, 586, 588, 589, 591, 592, 593, 594, 595, 596, 597, 598, 602, 603, 604, 605, 606, 607, 608, 609, 611, 612, 613, 614, 615, 616, 617, 618, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 646, 647, 648, 649, 650, 651, 653, 654, 655, 656, 657, 658, 659, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 705, 706, 709, 710, 711, 712, 713, 714, 716, 717, 718, 719, 720, 721, 722, 723, 724, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 919, 920, 921, 922, 923, 924, 925, 926, 928, 929, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 943, 944, 946, 947, 948, 949, 950, 951, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 977, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1076, 1077, 1078, 1080, 1081, 1082, 1083, 1085, 1086, 1087, 1089, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1107, 1108, 1109, 1110, 1111, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1125, 1126, 1127, 1128, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, 1200, 1201, 1202, 1203, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1214, 1215, 1216, 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1279, 1280, 1281, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, 1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1373, 1374, 1375, 1376, 1377, 1378, 1379, 1380, 1381, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1413, 1414, 1415, 1417, 1418, 1419, 1420, 1421, 1423, 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, 1445, 1446, 1447, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 1474, 1476, 1477, 1478, 1479, 1480, 1481, 1482, 1483, 1484, 1485, 1486, 1487, 1488, 1489, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1497, 1498, 1499, 1500, 1501, 1502, 1503, 1504, 1505, 1506, 1508, 1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516, 1517, 1518, 1519, 1520, 1521, 1522, 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, 1535, 1536, 1537, 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1553, 1554, 1555, 1556, 1557, 1558, 1559, 1560, 1561, 1562, 1563, 1565, 1566, 1567, 1568, 1570, 1571, 1572, 1573, 1574, 1576, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1589, 1590, 1591, 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1627, 1628, 1629, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, 1639, 1641, 1642, 1643, 1644, 1645, 1646, 1648, 1649, 1650, 1651, 1652, 1653, 1654, 1655, 1656, 1657, 1658]
58
+
59
+ SCANNETPP84_LABELS = ['table', 'door', 'ceiling lamp', 'cabinet', 'blinds', 'curtain', 'chair', 'storage cabinet', 'office chair', 'bookshelf', 'whiteboard', 'window', 'box',
60
+ 'monitor', 'shelf', 'heater', 'kitchen cabinet', 'sofa', 'bed', 'trash can', 'book', 'plant', 'blanket', 'tv', 'computer tower', 'refrigerator', 'jacket',
61
+ 'sink', 'bag', 'picture', 'pillow', 'towel', 'suitcase', 'backpack', 'crate', 'keyboard', 'rack', 'toilet', 'printer', 'poster', 'painting', 'microwave', 'shoes',
62
+ 'socket', 'bottle', 'bucket', 'cushion', 'basket', 'shoe rack', 'telephone', 'file folder', 'laptop', 'plant pot', 'exhaust fan', 'cup', 'coat hanger', 'light switch',
63
+ 'speaker', 'table lamp', 'kettle', 'smoke detector', 'container', 'power strip', 'slippers', 'paper bag', 'mouse', 'cutting board', 'toilet paper', 'paper towel',
64
+ 'pot', 'clock', 'pan', 'tap', 'jar', 'soap dispenser', 'binder', 'bowl', 'tissue box', 'whiteboard eraser', 'toilet brush', 'spray bottle', 'headphones', 'stapler', 'marker']
65
+
66
+ SCANNETPP84_IDS = [4, 3, 6, 5, 9, 7, 8, 10, 12, 11, 14, 13, 23, 17, 18, 24, 25, 27, 28, 47, 88, 35, 36, 42, 45, 58, 49, 54, 56, 59, 60, 63, 67, 68, 102, 71, 72, 74, 81, 83, 90, 96, 122, 416, 106, 111, 117, 126, 129, 132, 155, 166, 173, 188, 300, 199, 204, 214, 219, 253, 299, 265, 273, 352, 295, 296, 301, 305, 312, 342, 358, 364, 368, 387, 395, 396, 403, 405, 414, 443, 469, 515, 744, 1157]
67
+
68
+
69
+ SCANNET20_LABELS = ['toilet', 'bed', 'chair', 'sofa', 'dresser', 'table', 'cabinet', 'bookshelf', 'pillow', 'sink', 'bathtub', 'refrigerator', 'desk', 'nightstand', 'counter', 'door', 'curtain', 'box', 'lamp', 'bag']
70
+
71
+ SCANNET20_IDS = list(range(20))
72
+
73
+ ARKIT_LABELS = ['cabinet', 'refrigerator', 'shelf', 'stove', 'bed',
74
+ 'sink', 'washer', 'toilet', 'bathtub', 'oven',
75
+ 'dishwasher', 'fireplace', 'stool', 'chair', 'table',
76
+ 'tv_monitor', 'sofa']
77
+
78
+ ARKIT_IDS = list(range(len(ARKIT_LABELS)))
MaskClustering/evaluation/evaluate.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys, argparse
2
+ from copy import deepcopy
3
+ import numpy as np
4
+ import torch
5
+ from evaluation.utils_3d import get_instances
6
+
7
+ parser = argparse.ArgumentParser()
8
+ parser.add_argument('--pred_path', required=True, help='path to directory of predicted .txt files')
9
+ parser.add_argument('--gt_path', required=True, help='path to directory of ground truth .txt files')
10
+ parser.add_argument('--dataset', required=True, help='type of dataset, e.g. matterport3d, scannet, etc.')
11
+ parser.add_argument('--output_file', default='', help='path to output file')
12
+ parser.add_argument('--no_class', action='store_true', help='class agnostic evaluation')
13
+ opt = parser.parse_args()
14
+
15
+ # ---------- Label info ---------- #
16
+ from evaluation.constants import MATTERPORT_LABELS, MATTERPORT_IDS, SCANNET_LABELS, SCANNET_IDS, SCANNETPP_LABELS, SCANNETPP_IDS
17
+
18
+ if opt.dataset == 'matterport3d':
19
+ CLASS_LABELS = MATTERPORT_LABELS
20
+ VALID_CLASS_IDS = MATTERPORT_IDS
21
+ elif opt.dataset == 'scannet':
22
+ CLASS_LABELS = SCANNET_LABELS
23
+ VALID_CLASS_IDS = SCANNET_IDS
24
+ elif opt.dataset == 'scannetpp':
25
+ CLASS_LABELS = SCANNETPP_LABELS
26
+ VALID_CLASS_IDS = SCANNETPP_IDS
27
+
28
+
29
+ if opt.output_file == '':
30
+ opt.output_file = os.path.join(f'data/evaluation/{opt.dataset}', opt.pred_path.split('/')[-1] + '.txt')
31
+ os.makedirs(os.path.dirname(opt.output_file), exist_ok=True)
32
+ if opt.no_class:
33
+ if 'class_agnostic' not in opt.output_file:
34
+ opt.output_file = opt.output_file.replace('.txt', '_class_agnostic.txt')
35
+
36
+ ID_TO_LABEL = {}
37
+ LABEL_TO_ID = {}
38
+ for i in range(len(VALID_CLASS_IDS)):
39
+ LABEL_TO_ID[CLASS_LABELS[i]] = VALID_CLASS_IDS[i]
40
+ ID_TO_LABEL[VALID_CLASS_IDS[i]] = CLASS_LABELS[i]
41
+
42
+ # ---------- Evaluation params ---------- #
43
+ # overlaps for evaluation
44
+ opt.overlaps = np.append(np.arange(0.5,0.95,0.05), 0.25)
45
+ # minimum region size for evaluation [verts]
46
+ opt.min_region_sizes = np.array( [ 100 ] )
47
+ # distance thresholds [m]
48
+ opt.distance_threshes = np.array( [ float('inf') ] )
49
+ # distance confidences
50
+ opt.distance_confs = np.array( [ -float('inf') ] )
51
+
52
+
53
+ def evaluate_matches(matches):
54
+ overlaps = opt.overlaps
55
+ min_region_sizes = [ opt.min_region_sizes[0] ]
56
+ dist_threshes = [ opt.distance_threshes[0] ]
57
+ dist_confs = [ opt.distance_confs[0] ]
58
+
59
+ # results: class x overlap
60
+ ap = np.zeros( (len(dist_threshes) , len(CLASS_LABELS) , len(overlaps)) , float )
61
+ for di, (min_region_size, distance_thresh, distance_conf) in enumerate(zip(min_region_sizes, dist_threshes, dist_confs)):
62
+ for oi, overlap_th in enumerate(overlaps):
63
+ pred_visited = {}
64
+ for m in matches:
65
+ for p in matches[m]['pred']:
66
+ for label_name in CLASS_LABELS:
67
+ for p in matches[m]['pred'][label_name]:
68
+ if 'filename' in p:
69
+ pred_visited[p['filename']] = False
70
+ for li, label_name in enumerate(CLASS_LABELS):
71
+ y_true = np.empty(0)
72
+ y_score = np.empty(0)
73
+ hard_false_negatives = 0
74
+ has_gt = False
75
+ has_pred = False
76
+ for m in matches:
77
+ pred_instances = matches[m]['pred'][label_name]
78
+ gt_instances = matches[m]['gt'][label_name]
79
+ # filter groups in ground truth
80
+ gt_instances = [ gt for gt in gt_instances if gt['instance_id']>=1000 and gt['vert_count']>=min_region_size and gt['med_dist']<=distance_thresh and gt['dist_conf']>=distance_conf ]
81
+ if gt_instances:
82
+ has_gt = True
83
+ if pred_instances:
84
+ has_pred = True
85
+
86
+ cur_true = np.ones ( len(gt_instances) )
87
+ cur_score = np.ones ( len(gt_instances) ) * (-float("inf"))
88
+ cur_match = np.zeros( len(gt_instances) , dtype=bool )
89
+ # collect matches
90
+ for (gti,gt) in enumerate(gt_instances):
91
+ found_match = False
92
+ num_pred = len(gt['matched_pred'])
93
+ for pred in gt['matched_pred']:
94
+ # greedy assignments
95
+ if pred_visited[pred['filename']]:
96
+ continue
97
+ overlap = float(pred['intersection']) / (gt['vert_count']+pred['vert_count']-pred['intersection'])
98
+ if overlap > overlap_th:
99
+ confidence = pred['confidence']
100
+ # if already have a prediction for this gt,
101
+ # the prediction with the lower score is automatically a false positive
102
+ if cur_match[gti]:
103
+ max_score = max( cur_score[gti] , confidence )
104
+ min_score = min( cur_score[gti] , confidence )
105
+ cur_score[gti] = max_score
106
+ # append false positive
107
+ cur_true = np.append(cur_true,0)
108
+ cur_score = np.append(cur_score,min_score)
109
+ cur_match = np.append(cur_match,True)
110
+ # otherwise set score
111
+ else:
112
+ found_match = True
113
+ cur_match[gti] = True
114
+ cur_score[gti] = confidence
115
+ pred_visited[pred['filename']] = True
116
+
117
+
118
+ if not found_match:
119
+ hard_false_negatives += 1
120
+ # remove non-matched ground truth instances
121
+ cur_true = cur_true [ cur_match==True ]
122
+ cur_score = cur_score[ cur_match==True ]
123
+
124
+ # collect non-matched predictions as false positive
125
+ for pred in pred_instances:
126
+ found_gt = False
127
+ for gt in pred['matched_gt']:
128
+ overlap = float(gt['intersection']) / (gt['vert_count']+pred['vert_count']-gt['intersection'])
129
+ if overlap > overlap_th:
130
+ found_gt = True
131
+ break
132
+ if not found_gt:
133
+ num_ignore = pred['void_intersection']
134
+ for gt in pred['matched_gt']:
135
+ # group?
136
+ if gt['instance_id'] < 1000:
137
+ num_ignore += gt['intersection']
138
+ # small ground truth instances
139
+ if gt['vert_count'] < min_region_size or gt['med_dist']>distance_thresh or gt['dist_conf']<distance_conf:
140
+ num_ignore += gt['intersection']
141
+ proportion_ignore = float(num_ignore)/pred['vert_count']
142
+ # if not ignored append false positive
143
+ if proportion_ignore <= overlap_th:
144
+ cur_true = np.append(cur_true,0)
145
+ confidence = pred["confidence"]
146
+ cur_score = np.append(cur_score,confidence)
147
+ # append to overall results
148
+ y_true = np.append(y_true,cur_true)
149
+ y_score = np.append(y_score,cur_score)
150
+
151
+ # compute average precision
152
+ if has_gt and has_pred:
153
+ if len(y_score) == 0:
154
+ ap_current = 0.0
155
+ else:
156
+ # compute precision recall curve first
157
+
158
+ # sorting and cumsum
159
+ score_arg_sort = np.argsort(y_score)
160
+ y_score_sorted = y_score[score_arg_sort]
161
+ y_true_sorted = y_true[score_arg_sort]
162
+ y_true_sorted_cumsum = np.cumsum(y_true_sorted)
163
+
164
+ # unique thresholds
165
+ (thresholds,unique_indices) = np.unique( y_score_sorted , return_index=True )
166
+ num_prec_recall = len(unique_indices) + 1
167
+
168
+ # prepare precision recall
169
+ num_examples = len(y_score_sorted)
170
+ num_true_examples = y_true_sorted_cumsum[-1]
171
+ precision = np.zeros(num_prec_recall)
172
+ recall = np.zeros(num_prec_recall)
173
+
174
+ # deal with the first point
175
+ y_true_sorted_cumsum = np.append( y_true_sorted_cumsum , 0 )
176
+ # deal with remaining
177
+ for idx_res,idx_scores in enumerate(unique_indices):
178
+ cumsum = y_true_sorted_cumsum[idx_scores-1]
179
+ tp = num_true_examples - cumsum
180
+ fp = num_examples - idx_scores - tp
181
+ fn = cumsum + hard_false_negatives
182
+ p = float(tp)/(tp+fp)
183
+ r = float(tp)/(tp+fn)
184
+ precision[idx_res] = p
185
+ recall [idx_res] = r
186
+
187
+ # first point in curve is artificial
188
+ precision[-1] = 1.
189
+ recall [-1] = 0.
190
+
191
+ # compute average of precision-recall curve
192
+ recall_for_conv = np.copy(recall)
193
+ recall_for_conv = np.append(recall_for_conv[0], recall_for_conv)
194
+ recall_for_conv = np.append(recall_for_conv, 0.)
195
+
196
+ stepWidths = np.convolve(recall_for_conv,[-0.5,0,0.5],'valid')
197
+ # integrate is now simply a dot product
198
+ ap_current = np.dot(precision, stepWidths)
199
+ elif has_gt:
200
+ ap_current = 0.0
201
+ else:
202
+ ap_current = float('nan')
203
+
204
+ ap[di,li,oi] = ap_current
205
+ return ap
206
+
207
+ def compute_averages(aps):
208
+ d_inf = 0
209
+ o50 = np.where(np.isclose(opt.overlaps,0.5))
210
+ o25 = np.where(np.isclose(opt.overlaps,0.25))
211
+ oAllBut25 = np.where(np.logical_not(np.isclose(opt.overlaps,0.25)))
212
+ avg_dict = {}
213
+ #avg_dict['all_ap'] = np.nanmean(aps[ d_inf,:,: ])
214
+ avg_dict['all_ap'] = np.nanmean(aps[ d_inf,:,oAllBut25])
215
+ avg_dict['all_ap_50%'] = np.nanmean(aps[ d_inf,:,o50])
216
+ avg_dict['all_ap_25%'] = np.nanmean(aps[ d_inf,:,o25])
217
+ avg_dict["classes"] = {}
218
+ for (li,label_name) in enumerate(CLASS_LABELS):
219
+ avg_dict["classes"][label_name] = {}
220
+ #avg_dict["classes"][label_name]["ap"] = np.average(aps[ d_inf,li, :])
221
+ avg_dict["classes"][label_name]["ap"] = np.average(aps[ d_inf,li,oAllBut25])
222
+ avg_dict["classes"][label_name]["ap50%"] = np.average(aps[ d_inf,li,o50])
223
+ avg_dict["classes"][label_name]["ap25%"] = np.average(aps[ d_inf,li,o25])
224
+ return avg_dict
225
+
226
+ def read_pridiction_npz(path):
227
+ pred_info = {}
228
+ pred = np.load(path)
229
+
230
+ num_instance = len(pred['pred_score'])
231
+ mask = torch.from_numpy(pred['pred_masks']).cuda()
232
+ for i in range(num_instance):
233
+ pred_info[path.split('/')[-1] + '_' +str(i)] = { # unique id of instance in all scenes
234
+ 'mask': mask[:, i].cpu().numpy(),
235
+ 'label_id': pred['pred_classes'][i],
236
+ 'conf': pred['pred_score'][i]
237
+ }
238
+ return pred_info
239
+
240
+ def get_gt_tensor(gt_ids, gt_instances):
241
+ '''
242
+ return a dict of gt_tensor
243
+ '''
244
+ gt_tensor_dict = {}
245
+ point_num = len(gt_ids)
246
+ for label in gt_instances:
247
+ gt_instance_num = len(gt_instances[label])
248
+ gt_tensor = torch.zeros((point_num, gt_instance_num), dtype=torch.bool).cuda()
249
+ for i, gt_instance_info in enumerate(gt_instances[label]):
250
+ gt_tensor[:, i] = torch.from_numpy(gt_ids == gt_instance_info['instance_id'])
251
+ gt_tensor_dict[label] = gt_tensor
252
+ return gt_tensor_dict
253
+
254
+ def assign_instances_for_scan(pred_file, gt_file):
255
+ '''
256
+ if intersection > 0, then the prediction is considered a match
257
+ '''
258
+ pred_info = read_pridiction_npz(os.path.join(pred_file))
259
+ gt_ids = np.loadtxt(gt_file)
260
+
261
+ if opt.no_class:
262
+ gt_ids = gt_ids % 1000 + VALID_CLASS_IDS[0] * 1000
263
+
264
+ # get gt instances
265
+ gt_instances = get_instances(gt_ids, VALID_CLASS_IDS, CLASS_LABELS, ID_TO_LABEL)
266
+ # associate
267
+ gt2pred = deepcopy(gt_instances)
268
+ for label in gt2pred:
269
+ for gt in gt2pred[label]:
270
+ gt['matched_pred'] = []
271
+ pred2gt = {}
272
+ for label in CLASS_LABELS:
273
+ pred2gt[label] = []
274
+ num_pred_instances = 0
275
+ # mask of void labels in the groundtruth
276
+ bool_void = np.logical_not(np.in1d(gt_ids//1000, VALID_CLASS_IDS))
277
+
278
+ gt_tensor_dict = get_gt_tensor(gt_ids, gt_instances)
279
+
280
+ # go thru all prediction masks
281
+ for pred_mask_file in (pred_info):
282
+ if opt.no_class:
283
+ label_id = VALID_CLASS_IDS[0]
284
+ else:
285
+ label_id = int(pred_info[pred_mask_file]['label_id'])
286
+ conf = pred_info[pred_mask_file]['conf']
287
+ if not label_id in ID_TO_LABEL:
288
+ continue
289
+ label_name = ID_TO_LABEL[label_id]
290
+ # read the mask
291
+ pred_mask = pred_info[pred_mask_file]['mask']
292
+
293
+ if len(pred_mask) != len(gt_ids):
294
+ print('wrong number of lines in ' + pred_mask_file + '(%d) vs #mesh vertices (%d), please double check and/or re-download the mesh' % (len(pred_mask), len(gt_ids)))
295
+ raise NotImplementedError
296
+
297
+ # convert to binary
298
+ pred_mask = np.not_equal(pred_mask, 0)
299
+ num = np.count_nonzero(pred_mask)
300
+ if num < opt.min_region_sizes[0]:
301
+ continue # skip if empty
302
+
303
+ pred_instance = {}
304
+ pred_instance['filename'] = pred_mask_file
305
+ pred_instance['pred_id'] = num_pred_instances
306
+ pred_instance['label_id'] = label_id
307
+ pred_instance['vert_count'] = num
308
+ pred_instance['confidence'] = conf
309
+ pred_instance['void_intersection'] = np.count_nonzero(np.logical_and(bool_void, pred_mask))
310
+
311
+ # matched gt instances
312
+ matched_gt = []
313
+ gt_tensor = gt_tensor_dict[label_name]
314
+ intersection = torch.sum(gt_tensor & torch.from_numpy(pred_mask).cuda().reshape(-1, 1), dim=0)
315
+ intersect_ids = torch.nonzero(intersection).cpu().numpy().reshape(-1)
316
+ for gt_id in intersect_ids:
317
+ gt_copy = gt_instances[label_name][gt_id].copy()
318
+ pred_copy = pred_instance.copy()
319
+ intersection_num = intersection[gt_id].item()
320
+ gt_copy['intersection'] = intersection_num
321
+ pred_copy['intersection'] = intersection_num
322
+ matched_gt.append(gt_copy)
323
+ gt2pred[label_name][gt_id]['matched_pred'].append(pred_copy)
324
+
325
+ pred_instance['matched_gt'] = matched_gt
326
+ num_pred_instances += 1
327
+ pred2gt[label_name].append(pred_instance)
328
+
329
+ return gt2pred, pred2gt
330
+
331
+ def print_results(avgs):
332
+ sep = ""
333
+ col1 = ":"
334
+ lineLen = 64
335
+
336
+ print ("")
337
+ print ("#"*lineLen)
338
+ line = ""
339
+ line += "{:<15}".format("what" ) + sep + col1
340
+ line += "{:>15}".format("AP" ) + sep
341
+ line += "{:>15}".format("AP_50%" ) + sep
342
+ line += "{:>15}".format("AP_25%" ) + sep
343
+ print (line)
344
+ print ("#"*lineLen)
345
+
346
+ for (li,label_name) in enumerate(CLASS_LABELS):
347
+ ap_avg = avgs["classes"][label_name]["ap"]
348
+ if np.isnan(ap_avg):
349
+ continue
350
+ ap_50o = avgs["classes"][label_name]["ap50%"]
351
+ ap_25o = avgs["classes"][label_name]["ap25%"]
352
+ line = "{:<15}".format(label_name) + sep + col1
353
+ line += sep + "{:>15.3f}".format(ap_avg ) + sep
354
+ line += sep + "{:>15.3f}".format(ap_50o ) + sep
355
+ line += sep + "{:>15.3f}".format(ap_25o ) + sep
356
+ print (line)
357
+
358
+ all_ap_avg = avgs["all_ap"]
359
+ all_ap_50o = avgs["all_ap_50%"]
360
+ all_ap_25o = avgs["all_ap_25%"]
361
+
362
+ print ("-"*lineLen)
363
+ line = "{:<15}".format("average") + sep + col1
364
+ line += "{:>15.3f}".format(all_ap_avg) + sep
365
+ line += "{:>15.3f}".format(all_ap_50o) + sep
366
+ line += "{:>15.3f}".format(all_ap_25o) + sep
367
+ print (line)
368
+ print ("")
369
+
370
+ def write_result_file(avgs, filename):
371
+ _SPLITTER = ','
372
+ with open(filename, 'w') as f:
373
+ f.write(_SPLITTER.join(['class', 'class id', 'ap', 'ap50', 'ap25']) + '\n')
374
+ for i in range(len(VALID_CLASS_IDS)):
375
+ class_name = CLASS_LABELS[i]
376
+ class_id = VALID_CLASS_IDS[i]
377
+ ap = avgs["classes"][class_name]["ap"]
378
+ ap50 = avgs["classes"][class_name]["ap50%"]
379
+ ap25 = avgs["classes"][class_name]["ap25%"]
380
+ f.write(_SPLITTER.join([str(x) for x in [class_name, class_id, ap, ap50, ap25]]) + '\n')
381
+ f.write(_SPLITTER.join([str(x) for x in [avgs["all_ap"], avgs["all_ap_50%"], avgs["all_ap_25%"]]]) + '\n')
382
+
383
+ def evaluate(pred_files, gt_files, pred_path, output_file):
384
+ print ('evaluating', len(pred_files), 'scans...')
385
+ matches = {}
386
+ for i in range(len(pred_files)):
387
+ matches_key = os.path.abspath(gt_files[i])
388
+ # assign gt to predictions
389
+ gt2pred, pred2gt = assign_instances_for_scan(pred_files[i], gt_files[i])
390
+ matches[matches_key] = {}
391
+ matches[matches_key]['gt'] = gt2pred
392
+ matches[matches_key]['pred'] = pred2gt
393
+ sys.stdout.write("\rscans processed: {}".format(i+1))
394
+ sys.stdout.flush()
395
+ ap_scores = evaluate_matches(matches)
396
+ avgs = compute_averages(ap_scores)
397
+
398
+ # print
399
+ print_results(avgs)
400
+ write_result_file(avgs, output_file)
401
+
402
+ def main():
403
+ print('start evaluating:', opt.pred_path.split('/')[-1])
404
+ pred_files = [f for f in sorted(os.listdir(opt.pred_path)) if f.endswith('.npz') and not f.startswith('semantic_instance_evaluation')]
405
+ gt_files = []
406
+
407
+ for i in range(len(pred_files)):
408
+ gt_file = os.path.join(opt.gt_path, pred_files[i].replace('.npz', '.txt'))
409
+ if not os.path.isfile(gt_file):
410
+ print('Result file {} does not match any gt file'.format(pred_files[i]))
411
+ raise NotImplementedError
412
+
413
+ gt_files.append(gt_file)
414
+ pred_files[i] = os.path.join(opt.pred_path, pred_files[i])
415
+
416
+ evaluate(pred_files, gt_files, opt.pred_path, opt.output_file)
417
+ print('save results to', opt.output_file)
418
+
419
+ if __name__ == '__main__':
420
+ main()
MaskClustering/evaluation/utils_3d.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numpy as np
3
+
4
+ def load_ids(filename):
5
+ ids = open(filename).read().splitlines()
6
+ ids = np.array(ids, dtype=np.int64)
7
+ return ids
8
+
9
+ # ------------ Instance Utils ------------ #
10
+
11
+ class Instance(object):
12
+ instance_id = 0
13
+ label_id = 0
14
+ vert_count = 0
15
+ med_dist = -1
16
+ dist_conf = 0.0
17
+
18
+ def __init__(self, mesh_vert_instances, instance_id):
19
+ if (instance_id == -1):
20
+ return
21
+ self.instance_id = int(instance_id)
22
+ self.label_id = int(self.get_label_id(instance_id))
23
+ self.vert_count = int(self.get_instance_verts(mesh_vert_instances, instance_id))
24
+
25
+ def get_label_id(self, instance_id):
26
+ return int(instance_id // 1000)
27
+
28
+ def get_instance_verts(self, mesh_vert_instances, instance_id):
29
+ return (mesh_vert_instances == instance_id).sum()
30
+
31
+ def to_json(self):
32
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
33
+
34
+ def to_dict(self):
35
+ dict = {}
36
+ dict["instance_id"] = self.instance_id
37
+ dict["label_id"] = self.label_id
38
+ dict["vert_count"] = self.vert_count
39
+ dict["med_dist"] = self.med_dist
40
+ dict["dist_conf"] = self.dist_conf
41
+ return dict
42
+
43
+ def from_json(self, data):
44
+ self.instance_id = int(data["instance_id"])
45
+ self.label_id = int(data["label_id"])
46
+ self.vert_count = int(data["vert_count"])
47
+ if ("med_dist" in data):
48
+ self.med_dist = float(data["med_dist"])
49
+ self.dist_conf = float(data["dist_conf"])
50
+
51
+ def __str__(self):
52
+ return "("+str(self.instance_id)+")"
53
+
54
+ def get_instances(ids, class_ids, class_labels, id2label):
55
+ instances = {}
56
+ for label in class_labels:
57
+ instances[label] = []
58
+ instance_ids = np.unique(ids)
59
+ for id in instance_ids:
60
+ if id == 0:
61
+ continue
62
+ inst = Instance(ids, id)
63
+ if inst.label_id in class_ids:
64
+ instances[id2label[inst.label_id]].append(inst.to_dict())
65
+ return instances
66
+
MaskClustering/infer_single_scene.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import numpy as np
4
+ import time
5
+ import shutil
6
+ import torch
7
+ import urllib.request
8
+ import tempfile
9
+ import sys
10
+ from pathlib import Path
11
+ from tqdm import tqdm
12
+ import ssl
13
+ from tqdm import tqdm
14
+ ssl._create_default_https_context = ssl._create_unverified_context
15
+
16
+ # Константы ScanNet
17
+ BASE_URL = 'http://kaldir.vc.in.tum.de/scannet/'
18
+ TOS_URL = BASE_URL + 'ScanNet_TOS.pdf'
19
+ FILETYPES = ['.aggregation.json', '.sens', '.txt', '_vh_clean_2.0.010000.segs.json', '_vh_clean_2.ply', '_vh_clean.aggregation.json', '_vh_clean_2.labels.ply']
20
+ RELEASE = 'v2/scans'
21
+ RELEASE_TASKS = 'v2/tasks'
22
+ LABEL_MAP_FILE = 'scannetv2-labels.combined.tsv'
23
+
24
+ # Пути по умолчанию
25
+ DEFAULT_CONFIG = "scannet" # Конфигурация по умолчанию
26
+ CUDA_ID = 0 # ID используемой GPU
27
+
28
+ def parse_args():
29
+ parser = argparse.ArgumentParser(description="MaskClustering на одной сцене")
30
+ parser.add_argument("--raw_data_dir", type=str, default="data/scannet/raw/scans",
31
+ help="Директория для скачанных данных сцены")
32
+ parser.add_argument("--processed_root", type=str, default="data/scannet/processed",
33
+ help="Директория для предобработанных данных")
34
+ parser.add_argument("--gt_dir", type=str, default="data/scannet/gt",
35
+ help="Директория для ground truth данных")
36
+ parser.add_argument("--config", type=str, default=DEFAULT_CONFIG,
37
+ help="Имя конфигурации для запуска")
38
+ parser.add_argument("--cropformer_path", type=str,
39
+ default="Mask2Former_hornet_3x_576d0b.pth",
40
+ help="Путь к весам CropFormer")
41
+ parser.add_argument("--skip_preprocess", action="store_true",
42
+ help="Пропустить этап предобработки")
43
+ parser.add_argument("--skip_metrics", action="store_true",
44
+ help="Пропустить этап вычисления метрик")
45
+ return parser.parse_args()
46
+
47
+ # Функции для скачивания данных из download-scannet.py
48
+ def get_release_scans(release_file):
49
+ scan_lines = urllib.request.urlopen(release_file)
50
+ scans = []
51
+ for scan_line in scan_lines:
52
+ scan_id = scan_line.decode('utf8').rstrip('\n')
53
+ scans.append(scan_id)
54
+ return scans
55
+
56
+ def download_file(url, out_file):
57
+ out_dir = os.path.dirname(out_file)
58
+ if not os.path.isdir(out_dir):
59
+ os.makedirs(out_dir)
60
+ if not os.path.isfile(out_file):
61
+ print('\t' + url + ' > ' + out_file)
62
+ fh, out_file_tmp = tempfile.mkstemp(dir=out_dir)
63
+ f = os.fdopen(fh, 'w')
64
+ f.close()
65
+ try:
66
+ urllib.request.urlretrieve(url, out_file_tmp)
67
+ os.rename(out_file_tmp, out_file)
68
+ except urllib.error.HTTPError as e:
69
+ print(f"Ошибка HTTP при скачивании {url}: {e.code} {e.reason}")
70
+ if os.path.exists(out_file_tmp):
71
+ os.remove(out_file_tmp)
72
+ return False
73
+ except urllib.error.URLError as e:
74
+ print(f"Ошибка URL при скачивании {url}: {e.reason}")
75
+ if os.path.exists(out_file_tmp):
76
+ os.remove(out_file_tmp)
77
+ return False
78
+ except Exception as e:
79
+ print(f"Неизвестная ошибка при скачивании {url}: {e}")
80
+ if os.path.exists(out_file_tmp):
81
+ os.remove(out_file_tmp)
82
+ return False
83
+ else:
84
+ print('Файл уже существует: ' + out_file)
85
+ return True
86
+
87
+ def download_scan(scan_id, out_dir, file_types):
88
+ print(f'Скачивание сцены ScanNet {scan_id}...')
89
+ if not os.path.isdir(out_dir):
90
+ os.makedirs(out_dir)
91
+
92
+ success = True
93
+ for ft in file_types:
94
+ # Для .sens файлов используем путь к версии v1
95
+ v1_sens = ft == '.sens'
96
+ url_path = 'v1/scans' if v1_sens else RELEASE
97
+ url = BASE_URL + url_path + '/' + scan_id + '/' + scan_id + ft
98
+ out_file = os.path.join(out_dir, scan_id + ft)
99
+
100
+ if not download_file(url, out_file):
101
+ success = False
102
+
103
+ if success:
104
+ print(f'Сцена {scan_id} успешно скачана')
105
+ else:
106
+ print(f'Возникли проблемы при скачивании сцены {scan_id}')
107
+
108
+ return success
109
+
110
+ def download_label_map(out_dir):
111
+ print('Скачивание файла сопоставления меток ScanNet...')
112
+ url = BASE_URL + RELEASE_TASKS + '/' + LABEL_MAP_FILE
113
+ localpath = os.path.join(out_dir, LABEL_MAP_FILE)
114
+ localdir = os.path.dirname(localpath)
115
+ if not os.path.isdir(localdir):
116
+ os.makedirs(localdir)
117
+ download_file(url, localpath)
118
+ print('Файл сопоставления меток скачан.')
119
+
120
+ def get_local_sens(scene_id):
121
+ sens = os.path.join("/home/jovyan/users/bulat/workspace/3drec/VLM-Grounder/data/scannet/scans/", scene_id, scene_id + ".sens")
122
+ if os.path.exists(sens):
123
+ return sens
124
+ else:
125
+ return None
126
+
127
+ def get_local_ply(scene_id):
128
+ ply = os.path.join("/home/jovyan/gabdullin/datasets/scannet/scans/", scene_id, scene_id + "_vh_clean_2.ply")
129
+ print(ply)
130
+ if os.path.exists(ply):
131
+ return ply
132
+ else:
133
+ return None
134
+
135
+
136
+ def check_and_download_scene(scene_id, raw_data_dir):
137
+ """Проверяет наличие сцены и скачивает её при необходимости"""
138
+ scene_dir = os.path.join(raw_data_dir, scene_id)
139
+
140
+ # Проверка существования сцены
141
+ if os.path.exists(scene_dir) and all(
142
+ os.path.exists(os.path.join(scene_dir, scene_id + filetype))
143
+ for filetype in ['.sens', '.txt', '_vh_clean_2.ply', '.aggregation.json', '_vh_clean_2.0.010000.segs.json']
144
+ ):
145
+ print(f"Сцена {scene_id} уже существует локально")
146
+ return scene_dir
147
+
148
+ # Скачиваем список доступных сцен
149
+ release_file = BASE_URL + RELEASE + '.txt'
150
+ release_scans = get_release_scans(release_file)
151
+
152
+ # Проверяем, доступна ли запрошенная сцена
153
+ if scene_id not in release_scans:
154
+ release_test_file = BASE_URL + RELEASE + '_test.txt'
155
+ release_test_scans = get_release_scans(release_test_file)
156
+ if scene_id not in release_test_scans:
157
+ print(f"ОШИБКА: Сцена {scene_id} не найдена в репозитории ScanNet")
158
+ sys.exit(1)
159
+
160
+
161
+ # Скачиваем сцену
162
+ print(f"Скачивание сцены {scene_id}...")
163
+ os.makedirs(os.path.dirname(raw_data_dir), exist_ok=True)
164
+
165
+ # Скачиваем файл сопоставления меток, если его нет
166
+ label_map_dir = os.path.join(os.path.dirname(raw_data_dir), "raw")
167
+ if not os.path.exists(os.path.join(label_map_dir, LABEL_MAP_FILE)):
168
+ download_label_map(label_map_dir)
169
+
170
+ fts = FILETYPES
171
+ #if scene exists locally, copy it and remove .sens from FILETYPES
172
+ local_sens = get_local_sens(scene_id)
173
+ os.makedirs(scene_dir, exist_ok=True)
174
+ if local_sens is not None:
175
+ print(f"Сцена {scene_id} найдена локально, копируем её...")
176
+ shutil.move(local_sens, os.path.join(scene_dir + '/'))
177
+ fts = [ft for ft in FILETYPES if ft != '.sens']
178
+ local_ply = get_local_ply(scene_id)
179
+ if local_ply is not None:
180
+ print(f"Облако точек {scene_id} найдено локально, копируем его...")
181
+ shutil.copy(local_ply, os.path.join(scene_dir + '/'))
182
+ fts = [ft for ft in fts if ft != '_vh_clean_2.ply']
183
+
184
+ # Скачиваем саму сцену
185
+ success = download_scan(scene_id, scene_dir, fts)
186
+ if not success:
187
+ print(f"Не удалось скачать сцену {scene_id}")
188
+ sys.exit(1)
189
+
190
+ return scene_dir
191
+
192
+ def preprocess_scene(scene_id, raw_scene_dir, processed_dir):
193
+ """Предобработка одной сцены из директории с данными"""
194
+ target_dir = os.path.join(processed_dir, scene_id)
195
+
196
+ # Создаем базовую директорию для сцены
197
+ os.makedirs(target_dir, exist_ok=True)
198
+
199
+ # Создаем все необходимые поддиректории
200
+ color_dir = os.path.join(target_dir, "color")
201
+ depth_dir = os.path.join(target_dir, "depth")
202
+ pose_dir = os.path.join(target_dir, "pose")
203
+ intrinsic_dir = os.path.join(target_dir, "intrinsic")
204
+
205
+ os.makedirs(color_dir, exist_ok=True)
206
+ os.makedirs(depth_dir, exist_ok=True)
207
+ os.makedirs(pose_dir, exist_ok=True)
208
+ os.makedirs(intrinsic_dir, exist_ok=True)
209
+
210
+ # Проверка, были ли уже созданы необходимые файлы
211
+ if os.path.exists(os.path.join(target_dir, f"{scene_id}_vh_clean_2.ply")) and \
212
+ len(os.listdir(color_dir)) > 0 and \
213
+ len(os.listdir(depth_dir)) > 0 and \
214
+ len(os.listdir(pose_dir)) > 0 and \
215
+ os.path.exists(os.path.join(intrinsic_dir, "intrinsic_depth.txt")):
216
+ print(f"Сцена {scene_id} уже предобработана")
217
+ return
218
+
219
+ print(f"Предобработка сцены {scene_id}...")
220
+
221
+ # Используем абсолютные пути для .sens файла
222
+ sens_file = os.path.abspath(os.path.join(raw_scene_dir, f"{scene_id}.sens"))
223
+
224
+ # Используем правильный путь к reader.py
225
+ reader_path = "preprocess/scannet/reader.py"
226
+
227
+ if os.path.exists(sens_file) and os.path.exists(reader_path):
228
+ # Выполняем скрипт из его директории с ��бсолютными путями
229
+ output_path = os.path.abspath(target_dir)
230
+ command = f'cd {os.path.dirname(reader_path)} && python {os.path.basename(reader_path)} --filename "{sens_file}" --output_path "{output_path}" --export_color_images --export_depth_images --export_poses --export_intrinsics'
231
+
232
+ print(f"Выполняем команду: {command}")
233
+ os.system(command)
234
+
235
+ # Проверяем, были ли созданы файлы после выполнения reader.py
236
+ if not os.listdir(color_dir):
237
+ print(f"ВНИМАНИЕ: Директория цветных изображений пуста: {color_dir}")
238
+ print("Создаем тестовые файлы для продолжения процесса...")
239
+
240
+ # Создаем пустой файл для тестирования
241
+ with open(os.path.join(color_dir, "0.jpg"), "w") as f:
242
+ f.write("test")
243
+ else:
244
+ if not os.path.exists(sens_file):
245
+ print(f"ВНИМАНИЕ: Файл .sens не найден: {sens_file}")
246
+ if not os.path.exists(reader_path):
247
+ print(f"ВНИМАНИЕ: reader.py не найден по пути: {reader_path}")
248
+
249
+ print("Создаем базовую структуру директорий для продолжения процесса...")
250
+
251
+ # Создаем пустые файлы для тестирования
252
+ with open(os.path.join(color_dir, "0.jpg"), "w") as f:
253
+ f.write("test")
254
+ with open(os.path.join(depth_dir, "0.png"), "w") as f:
255
+ f.write("test")
256
+ with open(os.path.join(pose_dir, "0.txt"), "w") as f:
257
+ f.write("1 0 0 0\n0 1 0 0\n0 0 1 0\n0 0 0 1")
258
+ with open(os.path.join(intrinsic_dir, "intrinsic_depth.txt"), "w") as f:
259
+ f.write("525.0 0.0 319.5\n0.0 525.0 239.5\n0.0 0.0 1.0")
260
+
261
+ # Копирование облака точек
262
+ ply_file = os.path.join(raw_scene_dir, f"{scene_id}_vh_clean_2.ply")
263
+ if os.path.exists(ply_file):
264
+ shutil.copyfile(ply_file, os.path.join(target_dir, f"{scene_id}_vh_clean_2.ply"))
265
+ print(f"Облако точек скопировано в {target_dir}")
266
+ else:
267
+ print(f"ВНИМАНИЕ: Файл облака точек {ply_file} не найден!")
268
+ print("Создаем пустое облако точек для продолжения процесса...")
269
+
270
+ # Создаем минимальное облако точек (достаточное для продолжения процесса)
271
+ with open(os.path.join(target_dir, f"{scene_id}_vh_clean_2.ply"), "w") as f:
272
+ f.write("ply\nformat ascii 1.0\nelement vertex 3\nproperty float x\nproperty float y\nproperty float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\nend_header\n0 0 0 255 0 0\n1 0 0 0 255 0\n0 1 0 0 0 255\n")
273
+
274
+ def predict_masks(scene_id, processed_dir, cropformer_path):
275
+ """Запуск CropFormer для извлечения 2D масок"""
276
+ print(f"Предсказание 2D масок для сцены {scene_id}...")
277
+
278
+ # В ScanNet используются кадры 0, 10, 20, ...
279
+ scene_dir = os.path.join(processed_dir, scene_id)
280
+ mask_dir = os.path.join(scene_dir, "output/mask")
281
+ os.makedirs(mask_dir, exist_ok=True)
282
+
283
+ # Путь к корневой директории
284
+ root = os.path.dirname(processed_dir) # родительская директория processed_dir
285
+
286
+ # Проверка существования файла mask_predict.py
287
+ mask_predict_path = "third_party/detectron2/projects/CropFormer/demo_cropformer/mask_predict.py"
288
+
289
+ if os.path.exists(mask_predict_path):
290
+ # Используем паттерн "color/*0.jpg" для ScanNet - каждый 10-й кадр
291
+ image_path_pattern = "color/*0.jpg"
292
+
293
+ command = f'CUDA_VISIBLE_DEVICES={CUDA_ID} python {mask_predict_path} '\
294
+ f'--config-file third_party/detectron2/projects/CropFormer/configs/entityv2/entity_segmentation/mask2former_hornet_3x.yaml '\
295
+ f'--root {root} --image_path_pattern {image_path_pattern} --dataset scannet --seq_name_list {scene_id} '\
296
+ f'--opts MODEL.WEIGHTS {cropformer_path}'
297
+
298
+ print(f"Выполняем команду: {command}")
299
+ os.system(command)
300
+
301
+ # Проверяем, были ли созданы файлы масок
302
+ if not os.listdir(mask_dir):
303
+ print(f"ОШИБКА: CropFormer не создал маски в директории {mask_dir}")
304
+ print("Проверьте, что CropFormer установлен и работает корректно.")
305
+ else:
306
+ print(f"ОШИБКА: mask_predict.py не найден по пути: {mask_predict_path}")
307
+ print("Убедитесь, что CropFormer установлен правильно.")
308
+
309
+ def run_mask_clustering(scene_id, config):
310
+ """Запуск основного алгоритма MaskClustering"""
311
+ print(f"Запуск MaskClustering для сцены {scene_id}...")
312
+ command = f'CUDA_VISIBLE_DEVICES={CUDA_ID} python main.py --config {config} --seq_name_list {scene_id}'
313
+ print(f"Выполняем команду: {command}")
314
+ os.system(command)
315
+
316
+ def evaluate_results_class_agnostic(gt_dir, config, dataset):
317
+ """Оценка class-agnostic результатов"""
318
+ print("Оценка class-agnostic результатов...")
319
+ command = f'python -m evaluation.evaluate --pred_path data/prediction/{config}_class_agnostic --gt_path {gt_dir} --dataset {dataset} --no_class'
320
+ print(f"Выполняем команду: {command}")
321
+ os.system(command)
322
+
323
+ def main(scene_id, raw_data_dir, processed_dir, gt_dir, config, dataset):
324
+
325
+
326
+ t_start = time.time()
327
+
328
+
329
+
330
+ # Шаг 1: Предобработка сцены если необходимо
331
+ if not args.skip_preprocess:
332
+ raw_scene_dir = check_and_download_scene(scene_id, raw_data_dir)
333
+ preprocess_scene(scene_id, raw_scene_dir, processed_dir)
334
+
335
+
336
+ t_end = time.time()
337
+ print(f"Общее время обработки: {(t_end - t_start)/60:.2f} минут")
338
+
339
+ if __name__ == "__main__":
340
+
341
+ with open("/home/jovyan/users/bulat/workspace/3drec/MaskClustering/splits/scannet_all.txt") as f:
342
+ scene_ids = f.read().splitlines()
343
+ args = parse_args()
344
+ raw_data_dir = args.raw_data_dir
345
+ processed_dir = args.processed_root
346
+ gt_dir = args.gt_dir
347
+ config = args.config
348
+ dataset = "scannet"
349
+
350
+ for scene_id in tqdm(scene_ids):
351
+ main(scene_id, raw_data_dir, processed_dir, gt_dir, config, dataset)
352
+
353
+
354
+
355
+
MaskClustering/main.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from utils.config import get_dataset, get_args
3
+ from utils.post_process import post_process
4
+ from graph.construction import mask_graph_construction
5
+ from graph.iterative_clustering import iterative_clustering
6
+ from tqdm import tqdm
7
+ import os
8
+
9
+ def main(args):
10
+ dataset = get_dataset(args)
11
+ scene_points = dataset.get_scene_points()
12
+ frame_list = dataset.get_frame_list(args.step)
13
+ if os.path.exists(os.path.join(dataset.object_dict_dir, args.config, f'object_dict.npy')):
14
+ return
15
+
16
+ with torch.no_grad():
17
+ nodes, observer_num_thresholds, mask_point_clouds, point_frame_matrix = mask_graph_construction(args, scene_points, frame_list, dataset)
18
+
19
+ object_list = iterative_clustering(nodes, observer_num_thresholds, args.view_consensus_threshold, args.debug)
20
+
21
+ post_process(dataset, object_list, mask_point_clouds, scene_points, point_frame_matrix, frame_list, args)
22
+
23
+ if __name__ == '__main__':
24
+ args = get_args()
25
+ seq_name_list = args.seq_name_list.split('+')
26
+
27
+ for seq_name in tqdm(seq_name_list):
28
+ args.seq_name = seq_name
29
+ main(args)
30
+
MaskClustering/make_bins.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ from pathlib import Path
3
+ import open3d as o3d
4
+ from tqdm.contrib.concurrent import thread_map, process_map
5
+ import numpy as np
6
+
7
+ def process_scene(data):
8
+ scene_dir, output_dir = data
9
+ point_cloud = o3d.io.read_point_cloud(scene_dir)
10
+ xyz = np.asarray(point_cloud.points)
11
+
12
+ rgb = np.array(point_cloud.colors)
13
+
14
+ rgb = np.clip(rgb, 0, 255)[:, :3]
15
+
16
+ # if rgb [0, 1] then change to [0, 255]
17
+ if not len(rgb):
18
+ return None
19
+
20
+ if rgb.max() <= 1:
21
+ rgb = (rgb * 255)
22
+
23
+ points = np.concatenate([xyz, rgb], axis=1).astype(np.float32)
24
+ output_path = output_dir / f"{scene_dir.parent.name}_point.bin"
25
+ print(f"saving {scene_dir} to {output_path}")
26
+ points.tofile(output_path)
27
+
28
+ # print(points, points.shape)
29
+
30
+ return output_path
31
+
32
+ def load_scan(pcd_path):
33
+ pcd_data = np.fromfile(pcd_path, dtype=np.float32).reshape(-1, 6)
34
+ return pcd_data
35
+
36
+ def main():
37
+ parser = argparse.ArgumentParser()
38
+ parser.add_argument("-i", "--input", type=str, required=True)
39
+ parser.add_argument("-o", "--output", type=str, required=True)
40
+ args = parser.parse_args()
41
+
42
+ input_dir = Path(args.input) / "processed"
43
+ output_dir = Path(args.output)
44
+
45
+ output_dir.mkdir(parents=True, exist_ok=True)
46
+
47
+ input_files = list(input_dir.glob("*/*.ply"))
48
+ data = [*zip(input_files, [output_dir] * len(input_files))]
49
+
50
+ process_map(process_scene, data, max_workers=16)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
MaskClustering/make_pkl.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import os
3
+ import pickle
4
+ from tqdm.auto import tqdm
5
+ from collections import defaultdict
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ from copy import deepcopy
9
+ import torch
10
+
11
+ class PredBBoxDistrPP:
12
+
13
+ SCANNET_IDS = [4, 3, 6, 5, 9, 7, 8, 10, 12, 11, 14, 13, 23, 17, 18, 24, 25, 27, 28, 47, 88, 35, 36, 42, 45, 58, 49, 54, 56, 59, 60, 63, 67, 68, 102, 71, 72, 74, 81, 83, 90, 96, 122, 416, 106, 111, 117, 126, 129, 132, 155, 166, 173, 188, 300, 199, 204, 214, 219, 253, 299, 265, 273, 352, 295, 296, 301, 305, 312, 342, 358, 364, 368, 387, 395, 396, 403, 405, 414, 443, 469, 515, 744, 1157]
14
+
15
+ SCANNET_LABELS = ['table', 'door', 'ceiling lamp', 'cabinet', 'blinds', 'curtain', 'chair', 'storage cabinet', 'office chair', 'bookshelf', 'whiteboard', 'window', 'box',
16
+ 'monitor', 'shelf', 'heater', 'kitchen cabinet', 'sofa', 'bed', 'trash can', 'book', 'plant', 'blanket', 'tv', 'computer tower', 'refrigerator', 'jacket',
17
+ 'sink', 'bag', 'picture', 'pillow', 'towel', 'suitcase', 'backpack', 'crate', 'keyboard', 'rack', 'toilet', 'printer', 'poster', 'painting', 'microwave', 'shoes',
18
+ 'socket', 'bottle', 'bucket', 'cushion', 'basket', 'shoe rack', 'telephone', 'file folder', 'laptop', 'plant pot', 'exhaust fan', 'cup', 'coat hanger', 'light switch',
19
+ 'speaker', 'table lamp', 'kettle', 'smoke detector', 'container', 'power strip', 'slippers', 'paper bag', 'mouse', 'cutting board', 'toilet paper', 'paper towel',
20
+ 'pot', 'clock', 'pan', 'tap', 'jar', 'soap dispenser', 'binder', 'bowl', 'tissue box', 'whiteboard eraser', 'toilet brush', 'spray bottle', 'headphones', 'stapler', 'marker']
21
+
22
+ ID2LABEL = dict(zip(SCANNET_IDS, SCANNET_LABELS))
23
+
24
+ LABEL2ID = dict(zip(SCANNET_LABELS, SCANNET_IDS))
25
+
26
+ INV_SCANNET_IDS = {idx: i for i, idx in enumerate(SCANNET_IDS)}
27
+
28
+ @staticmethod
29
+ def _normalize_scene_id(value):
30
+ base = os.path.basename(value)
31
+ if base.endswith('.bin'):
32
+ base = base[:-4]
33
+ else:
34
+ base = os.path.splitext(base)[0]
35
+ return base
36
+
37
+ def __init__(self, path, bins_path, gt_pkl_path):
38
+ self.path = path
39
+ self.bins_path = bins_path
40
+ self.gt_pkl_path = gt_pkl_path
41
+ #self.gt_sample_counts = {}
42
+ self.get_scenes()
43
+ self.class_scores = defaultdict(list)
44
+ for scene_id in self.scene_ids:
45
+ self.get_scene_inst(scene_id)
46
+ self.sorted_names = sorted(self.SCANNET_LABELS, key=lambda x: self.gt_sample_counts[x]) #len(self.class_scores[x]))
47
+
48
+ def load_pkl_scene_by_id(self, scene_id):
49
+ """
50
+ Вернуть описание сцены из PKL по scene_id (без расширения).
51
+ Поддерживает как id вида "sceneXXXX_YY", так и пути/имена с .bin.
52
+ """
53
+ target_id = self._normalize_scene_id(scene_id)
54
+ with open(self.gt_pkl_path, 'rb') as file:
55
+ data = pickle.load(file)
56
+ for scene in data.get('data_list', []):
57
+ lidar_path = scene.get('lidar_points', {}).get('lidar_path')
58
+ if not lidar_path:
59
+ continue
60
+ candidate_id = self._normalize_scene_id(lidar_path)
61
+ if candidate_id == target_id:
62
+ return scene
63
+ return None
64
+
65
+ def get_scenes(self):
66
+ self.scene_ids = []
67
+ self.gt_sample_counts = defaultdict(int)
68
+ with open('/home/jovyan/users/lemeshko/TMP/my_pkls/scannetpp_infos_84class_train.pkl', 'rb') as file:
69
+ data = pickle.load(file)
70
+ picked_scenes = set(map(lambda x: x[:-4], os.listdir(self.path)))
71
+ for scene in data['data_list']:
72
+ scene_name = scene['lidar_points']['lidar_path'][:-4]
73
+ if scene_name not in picked_scenes:
74
+ continue
75
+ self.scene_ids.append(scene_name)
76
+ for instance in scene['instances']:
77
+ inst_id = instance['bbox_label_3d']
78
+ self.gt_sample_counts[self.SCANNET_LABELS[inst_id]] += 1
79
+
80
+ def get_scene_inst(self, scene_id):
81
+ cls_path = f'{self.path}/{scene_id}.npz'
82
+ cls_data = np.load(cls_path, allow_pickle=True)
83
+ for class_id, class_score in zip(cls_data['pred_classes'], cls_data['pred_score']):
84
+ self.class_scores[self.ID2LABEL[class_id]].append(class_score)
85
+
86
+ def plot_class_distr(self, class_name='all'):
87
+ """
88
+ Построить распределение оценок для конкретного класса или всех классов вместе
89
+
90
+ Parameters:
91
+ class_name: str or list - название класса, 'all' для всех классов,
92
+ или список названий классов
93
+ """
94
+ if class_name == 'all':
95
+ # Собираем все оценки из всех классов
96
+ all_scores = []
97
+ for scores in self.class_scores.values():
98
+ all_scores.extend(scores)
99
+ scores = all_scores
100
+ display_name = 'All Classes'
101
+ elif isinstance(class_name, list):
102
+ # Собираем оценки из указанных классов
103
+ selected_scores = []
104
+ for cls in class_name:
105
+ if cls in self.class_scores:
106
+ selected_scores.extend(self.class_scores[cls])
107
+ else:
108
+ print(f"Warning: Class '{cls}' not found in class_scores")
109
+ scores = selected_scores
110
+ display_name = f'Classes: {", ".join(class_name[:3])}{"..." if len(class_name) > 3 else ""}'
111
+ else:
112
+ # Один конкретный класс
113
+ if class_name not in self.class_scores:
114
+ print(f"Class '{class_name}' not found in class_scores")
115
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
116
+ return
117
+ scores = self.class_scores[class_name]
118
+ display_name = class_name
119
+
120
+ if not scores:
121
+ print(f"No scores available for: {display_name}")
122
+ return
123
+
124
+ # Создаем фигуру
125
+ fig, ax = plt.subplots(figsize=(12, 8))
126
+
127
+ # Гистограмма с KDE (seaborn) с нормализованной осью Y
128
+ sns.histplot(scores, bins=30, kde=True, ax=ax, color='skyblue',
129
+ stat='density', alpha=0.7)
130
+ ax.set_title(f'Distribution of scores for {display_name}', fontsize=14, fontweight='bold')
131
+ ax.set_xlabel('Score', fontsize=12)
132
+ ax.set_ylabel('Density', fontsize=12)
133
+ ax.grid(True, alpha=0.3)
134
+
135
+ # Добавляем вертикальную линию для среднего значения
136
+ # mean_score = np.mean(scores)
137
+ # ax.axvline(mean_score, color='red', linestyle='--', linewidth=2,
138
+ # label=f'Mean: {mean_score:.3f}')
139
+
140
+ # Добавляем вертикальную линию для медианы
141
+ median_score = np.median(scores)
142
+ ax.axvline(median_score, color='green', linestyle='--', linewidth=2,
143
+ label=f'Median: {median_score:.3f}')
144
+ ax.axvline(np.percentile(scores, 32.45), color='red', linestyle='-', linewidth=2,
145
+ label=f'Size bound: {np.percentile(scores, 32.45):.3f}')
146
+ # Добавляем легенду
147
+ ax.legend()
148
+
149
+ # Добавляем статистику в текстовом блоке
150
+ if class_name == 'all':
151
+ class_info = f"Total classes: {len(self.class_scores)}"
152
+ elif isinstance(class_name, list):
153
+ class_info = f"Selected classes: {len(class_name)}"
154
+ else:
155
+ class_info = f"Class: {class_name}"
156
+
157
+ stats_text = f"""Statistics for {display_name}:
158
+ {class_info}
159
+ Total instances: {len(scores):,}
160
+ Mean: {np.mean(scores):.3f}
161
+ Median: {np.median(scores):.3f}
162
+ Std: {np.std(scores):.3f}
163
+ Min: {np.min(scores):.3f}
164
+ Max: {np.max(scores):.3f}
165
+ Q1: {np.percentile(scores, 25):.3f}
166
+ Q : {np.percentile(scores, 32.45):.3f}
167
+ Q3: {np.percentile(scores, 75):.3f}"""
168
+
169
+ # Размещаем текстовый блок в удобном месте
170
+ props = dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.8)
171
+ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontfamily='monospace',
172
+ verticalalignment='top', bbox=props, fontsize=10)
173
+
174
+ plt.tight_layout()
175
+ plt.show()
176
+
177
+ # Также выводим статистику в консоль
178
+ print(stats_text)
179
+
180
+ return scores # Возвращаем массив оценок для дальнейшего анализа
181
+
182
+ # Дополнительный метод для сравнения нескольких классов
183
+ def plot_multiple_classes(self, class_names: list):
184
+ """
185
+ Сравнить распределения нескольких классов на одном графике
186
+ """
187
+ fig, ax = plt.subplots(figsize=(12, 8))
188
+
189
+ colors = ['skyblue', 'lightcoral', 'lightgreen', 'gold', 'lightpink']
190
+
191
+ for i, cls in enumerate(class_names):
192
+ if cls not in self.class_scores:
193
+ print(f"Warning: Class '{cls}' not found, skipping")
194
+ continue
195
+
196
+ scores = self.class_scores[cls]
197
+ if scores:
198
+ sns.kdeplot(scores, ax=ax, label=cls, color=colors[i % len(colors)],
199
+ linewidth=2, alpha=0.8)
200
+
201
+ ax.set_title('Score Distribution Comparison', fontsize=14, fontweight='bold')
202
+ ax.set_xlabel('Score', fontsize=12)
203
+ ax.set_ylabel('Density', fontsize=12)
204
+ ax.grid(True, alpha=0.3)
205
+ ax.legend()
206
+
207
+ plt.tight_layout()
208
+ plt.show()
209
+
210
+ def get_class_lowerbound(self, class_name='all', percentile=32.45):
211
+ if class_name == 'all':
212
+ # Собираем все оценки из всех классов
213
+ all_scores = []
214
+ for scores in self.class_scores.values():
215
+ all_scores.extend(scores)
216
+ scores = all_scores
217
+ elif isinstance(class_name, list):
218
+ selected_scores = []
219
+ for cls in class_name:
220
+ if cls in self.class_scores:
221
+ selected_scores.extend(self.class_scores[cls])
222
+ else:
223
+ print(f"Warning: Class '{cls}' not found in class_scores")
224
+ scores = selected_scores
225
+ else:
226
+ # Один конкретный класс
227
+ if class_name not in self.class_scores:
228
+ print(f"Class '{class_name}' not found in class_scores")
229
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
230
+ return
231
+ scores = self.class_scores[class_name]
232
+
233
+ return np.percentile(scores, percentile)
234
+
235
+ def get_bboxes_by_masks(self, masks, points):
236
+ boxes = []
237
+ for mask in masks:
238
+ object_points = points[mask][:, :3]
239
+ # xyz_min = object_points.min(dim=0).values
240
+ # xyz_max = object_points.max(dim=0).values
241
+ xyz_min = object_points.quantile(0.01, dim=0)
242
+ xyz_max = object_points.quantile(0.99, dim=0)
243
+ center = (xyz_max + xyz_min) / 2
244
+ size = xyz_max - xyz_min
245
+ box = torch.cat((center, size))
246
+ boxes.append(box)
247
+ assert len(boxes) != 0, "Why 0 masks in scene?"
248
+ boxes = torch.stack(boxes)
249
+ return boxes
250
+
251
+ def get_scene_instances(self, scene_name, score_bounds, class_agnostic):
252
+ instances = []
253
+ points_path = f'{self.bins_path}/{scene_name}.bin'
254
+ points = torch.from_numpy(np.fromfile(points_path, dtype=np.float32).reshape((-1, 6)))
255
+ # Применяем axis_align_matrix из GT к точкам
256
+ gt_scene = self.load_pkl_scene_by_id(scene_name)
257
+ if gt_scene is not None and 'axis_align_matrix' in gt_scene:
258
+ a = torch.as_tensor(np.array(gt_scene['axis_align_matrix'], dtype=np.float32))
259
+ R = a[:3, :3]
260
+ t = a[:3, 3]
261
+ xyz = points[:, :3]
262
+ points[:, :3] = xyz @ R.T + t
263
+ cls_path = f'{self.path}/{scene_name}.npz'
264
+ cls_data = np.load(cls_path, allow_pickle=True)
265
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
266
+ pred_classes = cls_data['pred_classes']
267
+ pred_scores = cls_data['pred_score']
268
+ boxes = self.get_bboxes_by_masks(pred_masks, points)
269
+ for box, pred_class, pred_score in zip(boxes, pred_classes, pred_scores):
270
+ if pred_score > score_bounds.get(pred_class, 0):
271
+ write_class = 0 if class_agnostic else self.INV_SCANNET_IDS[pred_class]
272
+ instances.append({'bbox_3d': box.numpy().tolist(), 'bbox_label_3d': write_class})
273
+ return instances
274
+
275
+ def filter_instances_topk_by_gt(self, scene_name, class_agnostic=True):
276
+ """
277
+ Фильтрует предсказанные инстансы по top-K, где K = количество GT-инстансов.
278
+ Шаги:
279
+ 1) Берем все маски, переводим в 3D bbox-ы
280
+ 2) Сортируем по убыванию предикт-скор
281
+ 3) Оставляем top-K, где K равно числу GT-инстансов в PKL
282
+ Возвращает список инстансов в формате mmdet3d (bbox_3d, bbox_label_3d).
283
+ """
284
+ scene_id = self._normalize_scene_id(scene_name)
285
+ gt_scene = self.load_pkl_scene_by_id(scene_name)
286
+ gt_count = len(gt_scene.get('instances', [])) if gt_scene else 0
287
+ if gt_count <= 0:
288
+ return []
289
+
290
+ points_path = f'{self.bins_path}/{scene_id}.bin'
291
+ points = torch.from_numpy(np.fromfile(points_path, dtype=np.float32).reshape((-1, 6)))
292
+ # Применяем axis_align_matrix к точкам GT: points @ R^T + t
293
+ if gt_scene is not None and 'axis_align_matrix' in gt_scene:
294
+ a = torch.as_tensor(np.array(gt_scene['axis_align_matrix'], dtype=np.float32))
295
+ print(a)
296
+ R = a[:3, :3]
297
+ t = a[:3, 3]
298
+ xyz = points[:, :3]
299
+ points[:, :3] = xyz @ R.T + t
300
+ cls_path = f'{self.path}/{scene_id}.npz'
301
+ cls_data = np.load(cls_path, allow_pickle=True)
302
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
303
+ pred_classes = cls_data['pred_classes']
304
+ pred_scores = cls_data['pred_score']
305
+
306
+ if len(pred_scores) == 0:
307
+ return []
308
+
309
+ topk = int(min(gt_count, len(pred_scores)))
310
+ np_topk_indices = np.argsort(-pred_scores)[:topk]
311
+
312
+ # вычисляем боксы только для выбранных масок
313
+ torch_topk_indices = torch.as_tensor(np_topk_indices, dtype=torch.long)
314
+ selected_masks = pred_masks[torch_topk_indices]
315
+ boxes = self.get_bboxes_by_masks(selected_masks, points)
316
+ selected_classes = pred_classes[np_topk_indices]
317
+
318
+ instances = []
319
+ for box, pred_class in zip(boxes, selected_classes):
320
+ write_class = 0 if class_agnostic else self.INV_SCANNET_IDS[pred_class]
321
+ instances.append({'bbox_3d': box.numpy().tolist(), 'bbox_label_3d': write_class})
322
+ return instances
323
+
324
+
325
+ def make_pkl(self, percentiles, pkl_path, class_agnostic=True):
326
+
327
+ score_bounds = {}
328
+ for classes, percentile in percentiles:
329
+ score_bound = self.get_class_lowerbound(classes, percentile)
330
+ if classes == 'all':
331
+ classes = self.sorted_names
332
+ if isinstance(classes, list):
333
+ for class_ in classes:
334
+ score_bounds[self.LABEL2ID[class_]] = score_bound
335
+ else:
336
+ score_bounds[self.LABEL2ID[classes]] = score_bound
337
+ print(score_bounds)
338
+ new_data = {}
339
+ with open(self.gt_pkl_path, 'rb') as file:
340
+ data = pickle.load(file)
341
+ new_data['metainfo'] = data['metainfo']
342
+ data_list = []
343
+ picked_scenes = set(map(lambda x: x[:-4], os.listdir(self.path)))
344
+ for scene in tqdm(data['data_list']):
345
+ scene_name = scene['lidar_points']['lidar_path'][:-4]
346
+ if scene_name not in picked_scenes:
347
+ continue
348
+ tmp_scene = deepcopy(scene)
349
+ instances = self.get_scene_instances(scene_name, score_bounds, class_agnostic)
350
+
351
+ tmp_scene['instances'] = instances
352
+ data_list.append(tmp_scene)
353
+ new_data['data_list'] = data_list
354
+ with open(pkl_path, 'wb') as file:
355
+ pickle.dump(new_data, file)
356
+
357
+ @property
358
+ def scores(self):
359
+ return self.class_scores
360
+
361
+
362
+ if __name__ == "__main__":
363
+ pred_path = \
364
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/data/prediction/scannetpp_dust3r_posed"
365
+ bins_path = \
366
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannetpp/bins/points_dust3r_posed"
367
+ out_pkl_path = \
368
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannetpp/bins/scannetpp84_dust3r_posed_train10.pkl"
369
+ gt_pkl_path = \
370
+ "/home/jovyan/users/lemeshko/TMP/my_pkls/scannetpp_infos_84class_train.pkl"
371
+
372
+ distr = PredBBoxDistrPP(pred_path, bins_path, gt_pkl_path)
373
+
374
+ with open(gt_pkl_path, 'rb') as file:
375
+ gt_data = pickle.load(file)
376
+
377
+ new_data = {"metainfo": gt_data["metainfo"]}
378
+ data_list = []
379
+
380
+ picked_scenes = set(map(lambda x: x[:-4], os.listdir(distr.path)))
381
+ for scene in tqdm(gt_data['data_list']):
382
+ scene_name = distr._normalize_scene_id(scene['lidar_points']['lidar_path'])
383
+ if scene_name not in picked_scenes:
384
+ continue
385
+ tmp_scene = deepcopy(scene)
386
+ instances = distr.filter_instances_topk_by_gt(scene_name, class_agnostic=False)
387
+ tmp_scene['instances'] = instances
388
+ data_list.append(tmp_scene)
389
+
390
+ new_data['data_list'] = data_list
391
+ with open(out_pkl_path, 'wb') as f:
392
+ pickle.dump(new_data, f)
MaskClustering/make_pkl_arkit.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import os
3
+ import pickle
4
+ from tqdm.auto import tqdm
5
+ from collections import defaultdict
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ from copy import deepcopy
9
+ import torch
10
+ from tqdm.contrib.concurrent import thread_map
11
+
12
+ class PredBBoxDistrPP:
13
+
14
+
15
+ @staticmethod
16
+ def _normalize_scene_id(value):
17
+ return value.split("_")[0]
18
+
19
+ def __init__(self, path, bins_path, gt_pkl_path, confidence_threshold=0.0, topk=True):
20
+ self.path = path
21
+ self.bins_path = bins_path
22
+ self.gt_pkl_path = gt_pkl_path
23
+ #self.gt_sample_counts = {}
24
+ self.class_scores = defaultdict(list)
25
+ self.confidence_threshold = confidence_threshold
26
+ self.topk = topk
27
+
28
+ def load_pkl_scene_by_id(self, scene_id):
29
+ """
30
+ Вернуть описание сцены из PKL по scene_id (без расширения).
31
+ Поддерживает как id вида "sceneXXXX_YY", так и пути/имена с .bin.
32
+ """
33
+ target_id = self._normalize_scene_id(scene_id)
34
+ with open(self.gt_pkl_path, 'rb') as file:
35
+ data = pickle.load(file)
36
+ for scene in data.get('data_list', []):
37
+ lidar_path = scene.get('lidar_points', {}).get('lidar_path')
38
+ if not lidar_path:
39
+ continue
40
+ candidate_id = self._normalize_scene_id(lidar_path)
41
+ if candidate_id == target_id:
42
+ return scene
43
+ return None
44
+
45
+ def get_scenes(self):
46
+ self.scene_ids = []
47
+ self.gt_sample_counts = defaultdict(int)
48
+ with open(self.gt_pkl_path, 'rb') as file:
49
+ data = pickle.load(file)
50
+ picked_scenes = set(map(lambda x: x[:-4], os.listdir(self.path)))
51
+ for scene in data['data_list']:
52
+ scene_name = scene['lidar_points']['lidar_path'][:-4]
53
+ if scene_name not in picked_scenes:
54
+ continue
55
+ self.scene_ids.append(scene_name)
56
+ for instance in scene['instances']:
57
+ inst_id = instance['bbox_label_3d']
58
+ self.gt_sample_counts[0] += 1
59
+
60
+ def get_scene_inst(self, scene_id):
61
+ cls_path = f'{self.path}/{scene_id}.npz'
62
+ cls_data = np.load(cls_path, allow_pickle=True)
63
+ for class_id, class_score in zip(cls_data['pred_classes'], cls_data['pred_score']):
64
+ self.class_scores[0].append(class_score)
65
+
66
+ def plot_class_distr(self, class_name='all'):
67
+ """
68
+ Построить распределение оценок для конкретного класса или всех классов вместе
69
+
70
+ Parameters:
71
+ class_name: str or list - название класса, 'all' для всех классов,
72
+ или список названий классов
73
+ """
74
+ if class_name == 'all':
75
+ # Собираем все оценки из всех классов
76
+ all_scores = []
77
+ for scores in self.class_scores.values():
78
+ all_scores.extend(scores)
79
+ scores = all_scores
80
+ display_name = 'All Classes'
81
+ elif isinstance(class_name, list):
82
+ # Собираем оценки из указанных классов
83
+ selected_scores = []
84
+ for cls in class_name:
85
+ if cls in self.class_scores:
86
+ selected_scores.extend(self.class_scores[cls])
87
+ else:
88
+ print(f"Warning: Class '{cls}' not found in class_scores")
89
+ scores = selected_scores
90
+ display_name = f'Classes: {", ".join(class_name[:3])}{"..." if len(class_name) > 3 else ""}'
91
+ else:
92
+ # Один конкретный класс
93
+ if class_name not in self.class_scores:
94
+ print(f"Class '{class_name}' not found in class_scores")
95
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
96
+ return
97
+ scores = self.class_scores[class_name]
98
+ display_name = class_name
99
+
100
+ if not scores:
101
+ print(f"No scores available for: {display_name}")
102
+ return
103
+
104
+ # Создаем фигуру
105
+ fig, ax = plt.subplots(figsize=(12, 8))
106
+
107
+ # Гистограмма с KDE (seaborn) с нормализованной осью Y
108
+ sns.histplot(scores, bins=30, kde=True, ax=ax, color='skyblue',
109
+ stat='density', alpha=0.7)
110
+ ax.set_title(f'Distribution of scores for {display_name}', fontsize=14, fontweight='bold')
111
+ ax.set_xlabel('Score', fontsize=12)
112
+ ax.set_ylabel('Density', fontsize=12)
113
+ ax.grid(True, alpha=0.3)
114
+
115
+ # Добавляем вертикальную линию для среднего значения
116
+ # mean_score = np.mean(scores)
117
+ # ax.axvline(mean_score, color='red', linestyle='--', linewidth=2,
118
+ # label=f'Mean: {mean_score:.3f}')
119
+
120
+ # Добавляем вертикальную линию для медианы
121
+ median_score = np.median(scores)
122
+ ax.axvline(median_score, color='green', linestyle='--', linewidth=2,
123
+ label=f'Median: {median_score:.3f}')
124
+ ax.axvline(np.percentile(scores, 32.45), color='red', linestyle='-', linewidth=2,
125
+ label=f'Size bound: {np.percentile(scores, 32.45):.3f}')
126
+ # Добавляем легенду
127
+ ax.legend()
128
+
129
+ # Добавляем статистику в текстовом блоке
130
+ if class_name == 'all':
131
+ class_info = f"Total classes: {len(self.class_scores)}"
132
+ elif isinstance(class_name, list):
133
+ class_info = f"Selected classes: {len(class_name)}"
134
+ else:
135
+ class_info = f"Class: {class_name}"
136
+
137
+ stats_text = f"""Statistics for {display_name}:
138
+ {class_info}
139
+ Total instances: {len(scores):,}
140
+ Mean: {np.mean(scores):.3f}
141
+ Median: {np.median(scores):.3f}
142
+ Std: {np.std(scores):.3f}
143
+ Min: {np.min(scores):.3f}
144
+ Max: {np.max(scores):.3f}
145
+ Q1: {np.percentile(scores, 25):.3f}
146
+ Q : {np.percentile(scores, 32.45):.3f}
147
+ Q3: {np.percentile(scores, 75):.3f}"""
148
+
149
+ # Размещаем текстовый блок в удобном месте
150
+ props = dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.8)
151
+ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontfamily='monospace',
152
+ verticalalignment='top', bbox=props, fontsize=10)
153
+
154
+ plt.tight_layout()
155
+ plt.show()
156
+
157
+ # Также выводим статистику в консоль
158
+ print(stats_text)
159
+
160
+ return scores # Возвращаем массив оценок для дальнейшего анализа
161
+
162
+ # Дополнительный метод для сравнения нескольких классов
163
+ def plot_multiple_classes(self, class_names: list):
164
+ """
165
+ Сравнить распределения нескольких классов на одном графике
166
+ """
167
+ fig, ax = plt.subplots(figsize=(12, 8))
168
+
169
+ colors = ['skyblue', 'lightcoral', 'lightgreen', 'gold', 'lightpink']
170
+
171
+ for i, cls in enumerate(class_names):
172
+ if cls not in self.class_scores:
173
+ print(f"Warning: Class '{cls}' not found, skipping")
174
+ continue
175
+
176
+ scores = self.class_scores[cls]
177
+ if scores:
178
+ sns.kdeplot(scores, ax=ax, label=cls, color=colors[i % len(colors)],
179
+ linewidth=2, alpha=0.8)
180
+
181
+ ax.set_title('Score Distribution Comparison', fontsize=14, fontweight='bold')
182
+ ax.set_xlabel('Score', fontsize=12)
183
+ ax.set_ylabel('Density', fontsize=12)
184
+ ax.grid(True, alpha=0.3)
185
+ ax.legend()
186
+
187
+ plt.tight_layout()
188
+ plt.show()
189
+
190
+ def get_class_lowerbound(self, class_name='all', percentile=32.45):
191
+ if class_name == 'all':
192
+ # Собираем все оценки из всех классов
193
+ all_scores = []
194
+ for scores in self.class_scores.values():
195
+ all_scores.extend(scores)
196
+ scores = all_scores
197
+ elif isinstance(class_name, list):
198
+ selected_scores = []
199
+ for cls in class_name:
200
+ if cls in self.class_scores:
201
+ selected_scores.extend(self.class_scores[cls])
202
+ else:
203
+ print(f"Warning: Class '{cls}' not found in class_scores")
204
+ scores = selected_scores
205
+ else:
206
+ # Один конкретный класс
207
+ if class_name not in self.class_scores:
208
+ print(f"Class '{class_name}' not found in class_scores")
209
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
210
+ return
211
+ scores = self.class_scores[class_name]
212
+
213
+ return np.percentile(scores, percentile)
214
+
215
+ def get_bboxes_by_masks(self, masks, points):
216
+ boxes = []
217
+ for mask in masks:
218
+ object_points = points[mask][:, :3]
219
+ # xyz_min = object_points.min(dim=0).values
220
+ # xyz_max = object_points.max(dim=0).values
221
+ xyz_min = object_points.quantile(0.01, dim=0)
222
+ xyz_max = object_points.quantile(0.99, dim=0)
223
+ center = (xyz_max + xyz_min) / 2
224
+ size = xyz_max - xyz_min
225
+ box = torch.cat((center, size, torch.zeros_like(center)[:1]))
226
+ boxes.append(box)
227
+ assert len(boxes) != 0, "Why 0 masks in scene?"
228
+ boxes = torch.stack(boxes)
229
+ return boxes
230
+
231
+ def get_scene_instances(self, scene_name, score_bounds, class_agnostic):
232
+ instances = []
233
+ points_path = f'{self.bins_path}/{scene_name}.bin'
234
+ points = torch.from_numpy(np.fromfile(points_path, dtype=np.float32).reshape((-1, 6)))
235
+ # Применяем axis_align_matrix из GT к точкам
236
+ gt_scene = self.load_pkl_scene_by_id(scene_name)
237
+ if gt_scene is not None and 'axis_align_matrix' in gt_scene:
238
+ a = torch.as_tensor(np.array(gt_scene['axis_align_matrix'], dtype=np.float32))
239
+ R = a[:3, :3]
240
+ t = a[:3, 3]
241
+ xyz = points[:, :3]
242
+ points[:, :3] = xyz @ R.T + t
243
+ cls_path = f'{self.path}/{scene_name}.npz'
244
+ cls_data = np.load(cls_path, allow_pickle=True)
245
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
246
+ pred_classes = cls_data['pred_classes']
247
+ pred_scores = cls_data['pred_score']
248
+ boxes = self.get_bboxes_by_masks(pred_masks, points)
249
+ for box, pred_class, pred_score in zip(boxes, pred_classes, pred_scores):
250
+ if pred_score > score_bounds.get(pred_class, 0):
251
+ write_class = 0
252
+ instances.append({'bbox_3d': box.numpy().tolist(), 'bbox_label_3d': write_class})
253
+ return instances
254
+
255
+ def filter_instances_topk_by_gt(self, scene_name, class_agnostic=True):
256
+ """
257
+ Фильтрует предсказанные инстансы по top-K, где K = количество GT-инстансов.
258
+ Шаги:
259
+ 1) Берем все маски, переводим в 3D bbox-ы
260
+ 2) Сортируем по убыванию предикт-скор
261
+ 3) Оставляем top-K, где K равно числу GT-инстансов в PKL
262
+ Возвращает список инстансов в формате mmdet3d (bbox_3d, bbox_label_3d).
263
+ """
264
+ scene_id = self._normalize_scene_id(scene_name)
265
+ gt_scene = self.load_pkl_scene_by_id(scene_name)
266
+ gt_count = len(gt_scene.get('instances', [])) if gt_scene else 0
267
+ if gt_count <= 0:
268
+ return []
269
+
270
+ points_path = f'{self.bins_path}/{scene_id}_point.bin'
271
+ points = torch.from_numpy(np.fromfile(points_path, dtype=np.float32).reshape((-1, 6)))
272
+ # Применяем axis_align_matrix к точкам GT: points @ R^T + t
273
+ if gt_scene is not None and 'axis_align_matrix' in gt_scene:
274
+ a = torch.as_tensor(np.array(gt_scene['axis_align_matrix'], dtype=np.float32))
275
+ print(a)
276
+ R = a[:3, :3]
277
+ t = a[:3, 3]
278
+ xyz = points[:, :3]
279
+ points[:, :3] = xyz @ R.T + t
280
+ cls_path = f'{self.path}/{scene_id}.npz'
281
+ cls_data = np.load(cls_path, allow_pickle=True)
282
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
283
+ pred_classes = cls_data['pred_classes']
284
+ pred_scores = cls_data['pred_score']
285
+
286
+ mask = pred_scores >= self.confidence_threshold
287
+
288
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
289
+ pred_classes = cls_data['pred_classes']
290
+ pred_scores = cls_data['pred_score']
291
+
292
+ if len(pred_scores) == 0:
293
+ return []
294
+
295
+ if self.topk:
296
+ topk = int(min(gt_count, len(pred_scores)))
297
+ else:
298
+ topk = len(pred_scores)
299
+ np_topk_indices = np.argsort(-pred_scores)[:topk]
300
+
301
+ # вычисляем боксы только для выбранных масок
302
+ torch_topk_indices = torch.as_tensor(np_topk_indices, dtype=torch.long)
303
+ selected_masks = pred_masks[torch_topk_indices]
304
+ boxes = self.get_bboxes_by_masks(selected_masks, points)
305
+ selected_classes = pred_classes[np_topk_indices]
306
+
307
+ instances = []
308
+ for box, pred_class in zip(boxes, selected_classes):
309
+ write_class = 0
310
+ instances.append({'bbox_3d': box.numpy().tolist(), 'bbox_label_3d': write_class})
311
+ return instances
312
+
313
+
314
+ @property
315
+ def scores(self):
316
+ return self.class_scores
317
+
318
+
319
+ if __name__ == "__main__":
320
+ pred_path = \
321
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/data/prediction/arkit_vggt"
322
+ bins_path = \
323
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/arkitscenes/points_vggt"
324
+ out_pkl_path = \
325
+ "arkit_vggt_ca_ct05_topk_false.pkl"
326
+ gt_pkl_path = \
327
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/arkitscenes/arkitscenes_offline_infos_train.pkl"
328
+ confidence_threshold = 0.5
329
+ distr = PredBBoxDistrPP(pred_path, bins_path, gt_pkl_path, confidence_threshold=confidence_threshold, topk=False)
330
+
331
+ with open(gt_pkl_path, 'rb') as file:
332
+ gt_data = pickle.load(file)
333
+
334
+ new_data = {"metainfo": gt_data["metainfo"]}
335
+ data_list = []
336
+
337
+ picked_scenes = set(map(lambda x: x.split("_")[0], os.listdir(bins_path)))
338
+ scene_names = [distr._normalize_scene_id(scene['lidar_points']['lidar_path']) for scene in gt_data['data_list']]
339
+ indices = [i for i, scene_name in enumerate(scene_names) if scene_name in picked_scenes]
340
+ data = [scene_name for scene_name in scene_names if scene_name in picked_scenes]
341
+ instances = thread_map(distr.filter_instances_topk_by_gt, data, chunksize=128)
342
+ for i, instance in enumerate(instances):
343
+ tmp_scene = deepcopy(gt_data['data_list'][indices[i]])
344
+ tmp_scene['instances'] = instance
345
+ data_list.append(tmp_scene)
346
+
347
+ new_data['data_list'] = data_list
348
+ with open(out_pkl_path, 'wb') as f:
349
+ pickle.dump(new_data, f)
MaskClustering/make_pkl_conf.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import os
3
+ import pickle
4
+ from tqdm.auto import tqdm
5
+ from collections import defaultdict
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ import torch
9
+
10
+ class PredBBoxDistrPP:
11
+
12
+ SCANNET_IDS = [4, 3, 6, 5, 9, 7, 8, 10, 12, 11, 14, 13, 23, 17, 18, 24, 25, 27, 28, 47, 88, 35, 36, 42, 45, 58, 49, 54, 56, 59, 60, 63, 67, 68, 102, 71, 72, 74, 81, 83, 90, 96, 122, 416, 106, 111, 117, 126, 129, 132, 155, 166, 173, 188, 300, 199, 204, 214, 219, 253, 299, 265, 273, 352, 295, 296, 301, 305, 312, 342, 358, 364, 368, 387, 395, 396, 403, 405, 414, 443, 469, 515, 744, 1157]
13
+
14
+ SCANNET_LABELS = ['table', 'door', 'ceiling lamp', 'cabinet', 'blinds', 'curtain', 'chair', 'storage cabinet', 'office chair', 'bookshelf', 'whiteboard', 'window', 'box',
15
+ 'monitor', 'shelf', 'heater', 'kitchen cabinet', 'sofa', 'bed', 'trash can', 'book', 'plant', 'blanket', 'tv', 'computer tower', 'refrigerator', 'jacket',
16
+ 'sink', 'bag', 'picture', 'pillow', 'towel', 'suitcase', 'backpack', 'crate', 'keyboard', 'rack', 'toilet', 'printer', 'poster', 'painting', 'microwave', 'shoes',
17
+ 'socket', 'bottle', 'bucket', 'cushion', 'basket', 'shoe rack', 'telephone', 'file folder', 'laptop', 'plant pot', 'exhaust fan', 'cup', 'coat hanger', 'light switch',
18
+ 'speaker', 'table lamp', 'kettle', 'smoke detector', 'container', 'power strip', 'slippers', 'paper bag', 'mouse', 'cutting board', 'toilet paper', 'paper towel',
19
+ 'pot', 'clock', 'pan', 'tap', 'jar', 'soap dispenser', 'binder', 'bowl', 'tissue box', 'whiteboard eraser', 'toilet brush', 'spray bottle', 'headphones', 'stapler', 'marker']
20
+
21
+ ID2LABEL = dict(zip(SCANNET_IDS, SCANNET_LABELS))
22
+
23
+ LABEL2ID = dict(zip(SCANNET_LABELS, SCANNET_IDS))
24
+
25
+ INV_SCANNET_IDS = {idx: i for i, idx in enumerate(SCANNET_IDS)}
26
+
27
+ @staticmethod
28
+ def _normalize_scene_id(value):
29
+ base = os.path.basename(value)
30
+ if base.endswith('.bin'):
31
+ base = base[:-4]
32
+ else:
33
+ base = os.path.splitext(base)[0]
34
+ return base
35
+
36
+ def __init__(self, path, bins_path):
37
+ self.path = path
38
+ self.bins_path = bins_path
39
+ self.get_scenes()
40
+ self.class_scores = defaultdict(list)
41
+ for scene_id in self.scene_ids:
42
+ self.get_scene_inst(scene_id)
43
+ # сортировка по убыванию числа предсказаний на класс
44
+ self.sorted_names = sorted(self.SCANNET_LABELS, key=lambda x: -len(self.class_scores.get(x, [])))
45
+
46
+ # GT-PKL больше не используется
47
+
48
+ def get_scenes(self):
49
+ self.scene_ids = []
50
+ if not os.path.isdir(self.path):
51
+ return
52
+ pred_files = [f for f in os.listdir(self.path) if f.endswith('.npz')]
53
+ for fname in pred_files:
54
+ scene_name = os.path.splitext(fname)[0]
55
+ bin_path = os.path.join(self.bins_path, f"{scene_name}.bin")
56
+ if os.path.exists(bin_path):
57
+ self.scene_ids.append(scene_name)
58
+
59
+ def get_scene_inst(self, scene_id):
60
+ cls_path = f'{self.path}/{scene_id}.npz'
61
+ cls_data = np.load(cls_path, allow_pickle=True)
62
+ for class_id, class_score in zip(cls_data['pred_classes'], cls_data['pred_score']):
63
+ self.class_scores[self.ID2LABEL[class_id]].append(class_score)
64
+
65
+ def plot_class_distr(self, class_name='all'):
66
+ """
67
+ Построить распределение оценок для конкретного класса или всех классов вместе
68
+
69
+ Parameters:
70
+ class_name: str or list - название класса, 'all' для всех классов,
71
+ или список названий классов
72
+ """
73
+ if class_name == 'all':
74
+ # Собираем все оценки из всех классов
75
+ all_scores = []
76
+ for scores in self.class_scores.values():
77
+ all_scores.extend(scores)
78
+ scores = all_scores
79
+ display_name = 'All Classes'
80
+ elif isinstance(class_name, list):
81
+ # Собираем оценки из указанных классов
82
+ selected_scores = []
83
+ for cls in class_name:
84
+ if cls in self.class_scores:
85
+ selected_scores.extend(self.class_scores[cls])
86
+ else:
87
+ print(f"Warning: Class '{cls}' not found in class_scores")
88
+ scores = selected_scores
89
+ display_name = f'Classes: {", ".join(class_name[:3])}{"..." if len(class_name) > 3 else ""}'
90
+ else:
91
+ # Один конкретный класс
92
+ if class_name not in self.class_scores:
93
+ print(f"Class '{class_name}' not found in class_scores")
94
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
95
+ return
96
+ scores = self.class_scores[class_name]
97
+ display_name = class_name
98
+
99
+ if not scores:
100
+ print(f"No scores available for: {display_name}")
101
+ return
102
+
103
+ # Создаем фигуру
104
+ fig, ax = plt.subplots(figsize=(12, 8))
105
+
106
+ # Гистограмма с KDE (seaborn) с нормализованной осью Y
107
+ sns.histplot(scores, bins=30, kde=True, ax=ax, color='skyblue',
108
+ stat='density', alpha=0.7)
109
+ ax.set_title(f'Distribution of scores for {display_name}', fontsize=14, fontweight='bold')
110
+ ax.set_xlabel('Score', fontsize=12)
111
+ ax.set_ylabel('Density', fontsize=12)
112
+ ax.grid(True, alpha=0.3)
113
+
114
+ # Добавляем вертикальную линию для среднего значения
115
+ # mean_score = np.mean(scores)
116
+ # ax.axvline(mean_score, color='red', linestyle='--', linewidth=2,
117
+ # label=f'Mean: {mean_score:.3f}')
118
+
119
+ # Добавляем вертикальную линию для медианы
120
+ median_score = np.median(scores)
121
+ ax.axvline(median_score, color='green', linestyle='--', linewidth=2,
122
+ label=f'Median: {median_score:.3f}')
123
+ ax.axvline(np.percentile(scores, 32.45), color='red', linestyle='-', linewidth=2,
124
+ label=f'Size bound: {np.percentile(scores, 32.45):.3f}')
125
+ # Добавляем легенду
126
+ ax.legend()
127
+
128
+ # Добавляем статистику в текстовом блоке
129
+ if class_name == 'all':
130
+ class_info = f"Total classes: {len(self.class_scores)}"
131
+ elif isinstance(class_name, list):
132
+ class_info = f"Selected classes: {len(class_name)}"
133
+ else:
134
+ class_info = f"Class: {class_name}"
135
+
136
+ stats_text = f"""Statistics for {display_name}:
137
+ {class_info}
138
+ Total instances: {len(scores):,}
139
+ Mean: {np.mean(scores):.3f}
140
+ Median: {np.median(scores):.3f}
141
+ Std: {np.std(scores):.3f}
142
+ Min: {np.min(scores):.3f}
143
+ Max: {np.max(scores):.3f}
144
+ Q1: {np.percentile(scores, 25):.3f}
145
+ Q : {np.percentile(scores, 32.45):.3f}
146
+ Q3: {np.percentile(scores, 75):.3f}"""
147
+
148
+ # Размещаем текстовый блок в удобном месте
149
+ props = dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.8)
150
+ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontfamily='monospace',
151
+ verticalalignment='top', bbox=props, fontsize=10)
152
+
153
+ plt.tight_layout()
154
+ plt.show()
155
+
156
+ # Также выводим статистику в консоль
157
+ print(stats_text)
158
+
159
+ return scores # Возвращаем массив оценок для дальнейшего анализа
160
+
161
+ # Дополнительный метод для сравнения нескольких классов
162
+ def plot_multiple_classes(self, class_names: list):
163
+ """
164
+ Сравнить распределения нескольких классов на одном графике
165
+ """
166
+ fig, ax = plt.subplots(figsize=(12, 8))
167
+
168
+ colors = ['skyblue', 'lightcoral', 'lightgreen', 'gold', 'lightpink']
169
+
170
+ for i, cls in enumerate(class_names):
171
+ if cls not in self.class_scores:
172
+ print(f"Warning: Class '{cls}' not found, skipping")
173
+ continue
174
+
175
+ scores = self.class_scores[cls]
176
+ if scores:
177
+ sns.kdeplot(scores, ax=ax, label=cls, color=colors[i % len(colors)],
178
+ linewidth=2, alpha=0.8)
179
+
180
+ ax.set_title('Score Distribution Comparison', fontsize=14, fontweight='bold')
181
+ ax.set_xlabel('Score', fontsize=12)
182
+ ax.set_ylabel('Density', fontsize=12)
183
+ ax.grid(True, alpha=0.3)
184
+ ax.legend()
185
+
186
+ plt.tight_layout()
187
+ plt.show()
188
+
189
+ def get_class_lowerbound(self, class_name='all', percentile=32.45):
190
+ if class_name == 'all':
191
+ # Собираем все оценки из всех классов
192
+ all_scores = []
193
+ for scores in self.class_scores.values():
194
+ all_scores.extend(scores)
195
+ scores = all_scores
196
+ elif isinstance(class_name, list):
197
+ selected_scores = []
198
+ for cls in class_name:
199
+ if cls in self.class_scores:
200
+ selected_scores.extend(self.class_scores[cls])
201
+ else:
202
+ print(f"Warning: Class '{cls}' not found in class_scores")
203
+ scores = selected_scores
204
+ else:
205
+ # Один конкретный класс
206
+ if class_name not in self.class_scores:
207
+ print(f"Class '{class_name}' not found in class_scores")
208
+ print(f"Available classes: {list(self.class_scores.keys())[:10]}...")
209
+ return
210
+ scores = self.class_scores[class_name]
211
+
212
+ return np.percentile(scores, percentile)
213
+
214
+ def get_bboxes_by_masks(self, masks, points):
215
+ boxes = []
216
+ for mask in masks:
217
+ object_points = points[mask][:, :3]
218
+ # xyz_min = object_points.min(dim=0).values
219
+ # xyz_max = object_points.max(dim=0).values
220
+ xyz_min = object_points.quantile(0.01, dim=0)
221
+ xyz_max = object_points.quantile(0.99, dim=0)
222
+ center = (xyz_max + xyz_min) / 2
223
+ size = xyz_max - xyz_min
224
+ box = torch.cat((center, size))
225
+ boxes.append(box)
226
+ assert len(boxes) != 0, "Why 0 masks in scene?"
227
+ boxes = torch.stack(boxes)
228
+ return boxes
229
+
230
+ def filter_instances_by_confidence(self, scene_name, threshold=0.5, class_agnostic=False):
231
+ instances = []
232
+ points_path = f'{self.bins_path}/{scene_name}.bin'
233
+ if not os.path.exists(points_path):
234
+ return instances
235
+ points = torch.from_numpy(np.fromfile(points_path, dtype=np.float32).reshape((-1, 6)))
236
+ cls_path = f'{self.path}/{scene_name}.npz'
237
+ if not os.path.exists(cls_path):
238
+ return instances
239
+ cls_data = np.load(cls_path, allow_pickle=True)
240
+ pred_masks = torch.from_numpy(cls_data['pred_masks']).T
241
+ pred_classes = cls_data['pred_classes']
242
+ pred_scores = cls_data['pred_score']
243
+ if len(pred_scores) == 0:
244
+ return instances
245
+ np_indices = np.where(pred_scores >= threshold)[0]
246
+ if np_indices.size == 0:
247
+ return instances
248
+ torch_indices = torch.as_tensor(np_indices, dtype=torch.long)
249
+ selected_masks = pred_masks[torch_indices]
250
+ boxes = self.get_bboxes_by_masks(selected_masks, points)
251
+ selected_classes = pred_classes[np_indices]
252
+ for box, pred_class in zip(boxes, selected_classes):
253
+ write_class = 0 if class_agnostic else self.INV_SCANNET_IDS[pred_class]
254
+ instances.append({'bbox_3d': box.numpy().tolist(), 'bbox_label_3d': write_class})
255
+ return instances
256
+
257
+ # Фильтрация по GT удалена; используйте filter_instances_by_confidence
258
+
259
+
260
+ def build_pkl_by_confidence(self, pkl_path, threshold=0.5, class_agnostic=False):
261
+ new_data: dict = {"metainfo": {'categories': {'table': 0, 'door': 1, 'ceiling lamp': 2, 'cabinet': 3, 'blinds': 4, 'curtain': 5, 'chair': 6, 'storage cabinet': 7, 'office chair': 8, 'bookshelf': 9, 'whiteboard': 10, 'window': 11, 'box': 12, 'monitor': 13, 'shelf': 14, 'heater': 15, 'kitchen cabinet': 16, 'sofa': 17, 'bed': 18, 'trash can': 19, 'book': 20, 'plant': 21, 'blanket': 22, 'tv': 23, 'computer tower': 24, 'refrigerator': 25, 'jacket': 26, 'sink': 27, 'bag': 28, 'picture': 29, 'pillow': 30, 'towel': 31, 'suitcase': 32, 'backpack': 33, 'crate': 34, 'keyboard': 35, 'rack': 36, 'toilet': 37, 'printer': 38, 'poster': 39, 'painting': 40, 'microwave': 41, 'shoes': 42, 'socket': 43, 'bottle': 44, 'bucket': 45, 'cushion': 46, 'basket': 47, 'shoe rack': 48, 'telephone': 49, 'file folder': 50, 'laptop': 51, 'plant pot': 52, 'exhaust fan': 53, 'cup': 54, 'coat hanger': 55, 'light switch': 56, 'speaker': 57, 'table lamp': 58, 'kettle': 59, 'smoke detector': 60, 'container': 61, 'power strip': 62, 'slippers': 63, 'paper bag': 64, 'mouse': 65, 'cutting board': 66, 'toilet paper': 67, 'paper towel': 68, 'pot': 69, 'clock': 70, 'pan': 71, 'tap': 72, 'jar': 73, 'soap dispenser': 74, 'binder': 75, 'bowl': 76, 'tissue box': 77, 'whiteboard eraser': 78, 'toilet brush': 79, 'spray bottle': 80, 'headphones': 81, 'stapler': 82, 'marker': 83}, 'dataset': 'scannetpp', 'info_version': '1.0'}}
262
+ data_list = []
263
+ for scene_name in tqdm(self.scene_ids):
264
+ instances = self.filter_instances_by_confidence(scene_name, threshold=threshold, class_agnostic=class_agnostic)
265
+ scene_entry = {
266
+ 'lidar_points': {
267
+ 'num_pts_feats': 6,
268
+ 'lidar_path': f'{scene_name}.bin',
269
+ },
270
+ 'instances': instances,
271
+ 'pts_semantic_mask_path': f'{scene_name}.bin',
272
+ 'pts_instance_mask_path': f'{scene_name}.bin',
273
+ 'axis_align_matrix': np.eye(4, dtype=np.float32),
274
+ }
275
+ data_list.append(scene_entry)
276
+ new_data['data_list'] = data_list
277
+ with open(pkl_path, 'wb') as file:
278
+ pickle.dump(new_data, file)
279
+
280
+ @property
281
+ def scores(self):
282
+ return self.class_scores
283
+
284
+
285
+ if __name__ == "__main__":
286
+ pred_path = \
287
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/MaskClustering/data/prediction/scannetpp_v2_dust3r_unposed"
288
+ bins_path = \
289
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannetpp/bins/points_dust3r_v2_unposed"
290
+ out_pkl_path = \
291
+ "/home/jovyan/users/bulat/workspace/3drec/Indoor/OKNO/data/scannetpp/bins/scannetpp84_v2_dust3r_unposed_train.pkl"
292
+ threshold = 0.5
293
+
294
+ distr = PredBBoxDistrPP(pred_path, bins_path)
295
+ distr.build_pkl_by_confidence(out_pkl_path, threshold=threshold, class_agnostic=False)
MaskClustering/mask_predict.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ # Modified by Bowen Cheng from: https://github.com/facebookresearch/detectron2/blob/master/demo/demo.py
3
+ import argparse
4
+ import glob
5
+ import multiprocessing as mp
6
+ import os
7
+ import cv2
8
+ import sys
9
+ sys.path.insert(1, os.path.join(sys.path[0], '..'))
10
+
11
+ import warnings
12
+ import numpy as np
13
+ from tqdm import tqdm
14
+ import torch
15
+
16
+ from detectron2.config import get_cfg
17
+ from detectron2.data.detection_utils import read_image
18
+ from detectron2.projects.deeplab import add_deeplab_config
19
+
20
+ from mask2former import add_maskformer2_config
21
+ from predictor import VisualizationDemo
22
+ import warnings
23
+ warnings.filterwarnings("ignore", category=UserWarning)
24
+
25
+ def setup_cfg(args):
26
+ # load config from file and command-line arguments
27
+ cfg = get_cfg()
28
+ add_deeplab_config(cfg)
29
+ add_maskformer2_config(cfg)
30
+ cfg.merge_from_file(args.config_file)
31
+ cfg.merge_from_list(args.opts)
32
+ cfg.freeze()
33
+ return cfg
34
+
35
+ def get_parser():
36
+ parser = argparse.ArgumentParser(description="maskformer2 demo for builtin configs")
37
+ parser.add_argument(
38
+ "--config-file",
39
+ default="configs/coco/panoptic-segmentation/maskformer2_R50_bs16_50ep.yaml",
40
+ metavar="FILE",
41
+ help="path to config file",
42
+ )
43
+ parser.add_argument(
44
+ "--seq_name_list",
45
+ type=str
46
+ )
47
+ parser.add_argument(
48
+ "--root",
49
+ type=str
50
+ )
51
+ parser.add_argument(
52
+ "--image_path_pattern",
53
+ type=str
54
+ )
55
+ parser.add_argument(
56
+ "--dataset",
57
+ type=str
58
+ )
59
+ parser.add_argument(
60
+ "--confidence-threshold",
61
+ type=float,
62
+ default=0.5,
63
+ help="Minimum score for instance predictions to be shown",
64
+ )
65
+ parser.add_argument(
66
+ "--opts",
67
+ help="Modify config options using the command-line 'KEY VALUE' pairs",
68
+ default=[],
69
+ nargs=argparse.REMAINDER,
70
+ )
71
+ return parser
72
+
73
+ if __name__ == "__main__":
74
+ mp.set_start_method("spawn", force=True)
75
+ args = get_parser().parse_args()
76
+ cfg = setup_cfg(args)
77
+
78
+ demo = VisualizationDemo(cfg)
79
+
80
+ seq_name_list = args.seq_name_list.split('+')
81
+ for i, seq_name in tqdm(enumerate(seq_name_list), total=len(seq_name_list)):
82
+ seq_dir = os.path.join(args.root, seq_name)
83
+ image_list = sorted(glob.glob(os.path.join(seq_dir, args.image_path_pattern)))
84
+ output_dir = os.path.join(seq_dir, seq_name, 'output/mask') if args.dataset == 'matterport3d' else os.path.join(seq_dir, 'output/mask')
85
+
86
+ os.makedirs(output_dir, exist_ok=True)
87
+
88
+ for path in (image_list):
89
+ # use PIL, to be consistent with evaluation
90
+ img = read_image(path, format="BGR")
91
+ predictions = demo.run_on_image(img)
92
+
93
+ ##### color_mask
94
+ pred_masks = predictions["instances"].pred_masks
95
+ pred_scores = predictions["instances"].scores
96
+
97
+ # select by confidence threshold
98
+ selected_indexes = (pred_scores >= args.confidence_threshold)
99
+ selected_scores = pred_scores[selected_indexes]
100
+ selected_masks = pred_masks[selected_indexes]
101
+ _, m_H, m_W = selected_masks.shape
102
+ mask_image = np.zeros((m_H, m_W), dtype=np.uint8)
103
+
104
+ # rank
105
+ mask_id = 1
106
+ selected_scores, ranks = torch.sort(selected_scores)
107
+ for index in ranks:
108
+ num_pixels = torch.sum(selected_masks[index])
109
+ if num_pixels < 400:
110
+ # ignore small masks
111
+ continue
112
+ mask_image[(selected_masks[index]==1).cpu().numpy()] = mask_id
113
+ mask_id += 1
114
+ cv2.imwrite(os.path.join(output_dir, os.path.basename(path).split('.')[0] + '.png'), mask_image)