adding real MK
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- MaskClustering +0 -1
- MaskClustering/arkit_gt_prep.py +154 -0
- MaskClustering/arkit_prep.py +137 -0
- MaskClustering/arkit_vggt_prep.py +154 -0
- MaskClustering/bins_build_pkl.py +36 -0
- MaskClustering/build_pkl.py +36 -0
- MaskClustering/cluster_masks.py +83 -0
- MaskClustering/configs/arkit_dust3r_posed.json +10 -0
- MaskClustering/configs/arkit_gt.json +10 -0
- MaskClustering/configs/arkit_gt_train.json +10 -0
- MaskClustering/configs/arkit_vggt.json +10 -0
- MaskClustering/configs/demo.json +10 -0
- MaskClustering/configs/itw.json +10 -0
- MaskClustering/configs/matterport3d.json +10 -0
- MaskClustering/configs/scannet.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_15.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_25.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_35.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_35_bulat.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_45.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_45_andrey.json +10 -0
- MaskClustering/configs/scannet_dust3r_posed_45_bulat.json +10 -0
- MaskClustering/configs/scannet_dust3r_unposed_15.json +10 -0
- MaskClustering/configs/scannet_dust3r_unposed_25.json +10 -0
- MaskClustering/configs/scannet_dust3r_unposed_35.json +10 -0
- MaskClustering/configs/scannet_dust3r_unposed_45.json +10 -0
- MaskClustering/configs/scannetpp.json +10 -0
- MaskClustering/configs/scannetpp_dust3r_filtered_depth.json +10 -0
- MaskClustering/configs/scannetpp_dust3r_posed.json +10 -0
- MaskClustering/configs/scannetpp_dust3r_unposed.json +10 -0
- MaskClustering/configs/scannetpp_mapanything_posed.json +10 -0
- MaskClustering/configs/scannetpp_v2_dust3r_posed.json +10 -0
- MaskClustering/configs/scannetpp_v2_dust3r_unposed.json +10 -0
- MaskClustering/configs/wild.json +10 -0
- MaskClustering/dataset/demo.py +100 -0
- MaskClustering/dataset/matterport.py +137 -0
- MaskClustering/dataset/scannet.py +452 -0
- MaskClustering/dataset/scannetpp.py +217 -0
- MaskClustering/dense_masks.py +91 -0
- MaskClustering/evaluation/__init__.py +1 -0
- MaskClustering/evaluation/constants.py +78 -0
- MaskClustering/evaluation/evaluate.py +420 -0
- MaskClustering/evaluation/utils_3d.py +66 -0
- MaskClustering/infer_single_scene.py +355 -0
- MaskClustering/main.py +30 -0
- MaskClustering/make_bins.py +54 -0
- MaskClustering/make_pkl.py +392 -0
- MaskClustering/make_pkl_arkit.py +349 -0
- MaskClustering/make_pkl_conf.py +295 -0
- 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)
|