YYYYYYUUU's picture
Backup FULL local core code incl. libs/ CUDA ext + all configs
3499c27 verified
Raw
History Blame Contribute Delete
10.3 kB
"""
Preprocessing Script for ScanNet 20/200
Author: Xiaoyang Wu (xiaoyang.wu.cs@gmail.com)
Please cite our work if the code is helpful to you.
"""
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
import os
import argparse
import glob
import json
import plyfile
import numpy as np
import pandas as pd
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor
from itertools import repeat
from pathlib import Path
# Load external constants
from meta_data.scannet200_constants import VALID_CLASS_IDS_200, VALID_CLASS_IDS_20
CLOUD_FILE_PFIX = "_vh_clean_2"
SEGMENTS_FILE_PFIX = ".0.010000.segs.json"
AGGREGATIONS_FILE_PFIX = ".aggregation.json"
CLASS_IDS200 = VALID_CLASS_IDS_200
CLASS_IDS20 = VALID_CLASS_IDS_20
IGNORE_INDEX = -1
def read_plymesh(filepath):
"""Read ply file and return it as numpy array. Returns None if emtpy."""
with open(filepath, "rb") as f:
plydata = plyfile.PlyData.read(f)
if plydata.elements:
vertices = pd.DataFrame(plydata["vertex"].data).values
faces = np.stack(plydata["face"].data["vertex_indices"], axis=0)
return vertices, faces
# Map the raw category id to the point cloud
def point_indices_from_group(seg_indices, group, labels_pd):
group_segments = np.array(group["segments"])
label = group["label"]
print(f"[PREPROCESS DEBUG] Processing raw label from .aggregation.json: '{label}'")
# 尝试用 'category' 列进行匹配
matched_rows = labels_pd[labels_pd["category"] == label]
print(f"[PREPROCESS DEBUG] Found {len(matched_rows)} matching rows in labels file for category '{label}'")
if len(matched_rows) > 0:
label_id20 = int(matched_rows.iloc[0]["nyu40id"])
label_id200 = int(matched_rows.iloc[0]["id"])
print(f"[PREPROCESS DEBUG] Successfully mapped to NYU40 ID: {label_id20}, ScanNet200 ID: {label_id200}")
else:
label_id20 = IGNORE_INDEX
label_id200 = IGNORE_INDEX
print(f"[PREPROCESS DEBUG] WARNING: Label '{label}' NOT FOUND in 'category' column!")
# 检查 ID 是否在有效列表中
is_valid_20 = label_id20 in CLASS_IDS20
is_valid_200 = label_id200 in CLASS_IDS200
print(f"[PREPROCESS DEBUG] Is NYU40 ID {label_id20} in VALID_CLASS_IDS_20? {is_valid_20}")
print(f"[PREPROCESS DEBUG] Is ScanNet200 ID {label_id200} in VALID_CLASS_IDS_200? {is_valid_200}")
if is_valid_20:
final_label_20 = CLASS_IDS20.index(label_id20)
print(f"[PREPROCESS DEBUG] Final ScanNet20 Label: {final_label_20}")
else:
final_label_20 = IGNORE_INDEX
print(f"[PREPROCESS DEBUG] Final ScanNet20 Label: IGNORE_INDEX (-1)")
if is_valid_200:
final_label_200 = CLASS_IDS200.index(label_id200)
print(f"[PREPROCESS DEBUG] Final ScanNet200 Label: {final_label_200}")
else:
final_label_200 = IGNORE_INDEX
print(f"[PREPROCESS DEBUG] Final ScanNet200 Label: IGNORE_INDEX (-1)")
# get points, where segment indices (points labelled with segment ids) are in the group segment list
point_idx = np.where(np.isin(seg_indices, group_segments))[0]
print(f"[PREPROCESS DEBUG] Assigned label to {len(point_idx)} points.\n")
return point_idx, final_label_20, final_label_200
def face_normal(vertex, face):
v01 = vertex[face[:, 1]] - vertex[face[:, 0]]
v02 = vertex[face[:, 2]] - vertex[face[:, 0]]
vec = np.cross(v01, v02)
length = np.sqrt(np.sum(vec**2, axis=1, keepdims=True)) + 1.0e-8
nf = vec / length
area = length * 0.5
return nf, area
def vertex_normal(vertex, face):
nf, area = face_normal(vertex, face)
nf = nf * area
nv = np.zeros_like(vertex)
for i in range(face.shape[0]):
nv[face[i]] += nf[i]
length = np.sqrt(np.sum(nv**2, axis=1, keepdims=True)) + 1.0e-8
nv = nv / length
return nv
def handle_process(
scene_path, output_path, labels_pd, train_scenes, val_scenes, parse_normals=True
):
scene_id = os.path.basename(scene_path)
mesh_path = os.path.join(scene_path, f"{scene_id}{CLOUD_FILE_PFIX}.ply")
segments_file = os.path.join(
scene_path, f"{scene_id}{CLOUD_FILE_PFIX}{SEGMENTS_FILE_PFIX}"
)
aggregations_file = os.path.join(scene_path, f"{scene_id}{AGGREGATIONS_FILE_PFIX}")
info_file = os.path.join(scene_path, f"{scene_id}.txt")
# 确定 split
if scene_id in train_scenes:
output_path = os.path.join(output_path, "train", f"{scene_id}")
split_name = "train"
elif scene_id in val_scenes:
output_path = os.path.join(output_path, "val", f"{scene_id}")
split_name = "val"
else:
output_path = os.path.join(output_path, "test", f"{scene_id}")
split_name = "test"
# 🚀 关键修改:跳过缺失的 .ply 文件
if not os.path.exists(mesh_path):
print(f"⚠️ 跳过缺失场景: {scene_id} (文件不存在: {mesh_path})")
return # 👈 直接返回,不处理这个场景
print(f"✅ Processing: {scene_id} in {split_name}")
try:
vertices, faces = read_plymesh(mesh_path)
coords = vertices[:, :3]
colors = vertices[:, 3:6]
save_dict = dict(
coord=coords.astype(np.float32),
color=colors.astype(np.uint8),
)
# Parse Normals
if parse_normals:
save_dict["normal"] = vertex_normal(coords, faces).astype(np.float32)
# Load segments and aggregations for train/val
if split_name != "test":
with open(segments_file) as f:
segments = json.load(f)
seg_indices = np.array(segments["segIndices"])
with open(aggregations_file) as f:
aggregation = json.load(f)
seg_groups = np.array(aggregation["segGroups"])
# Generate labels
semantic_gt20 = np.ones((vertices.shape[0]), dtype=np.int16) * IGNORE_INDEX
semantic_gt200 = np.ones((vertices.shape[0]), dtype=np.int16) * IGNORE_INDEX
instance_ids = np.ones((vertices.shape[0]), dtype=np.int16) * IGNORE_INDEX
for group in seg_groups:
point_idx, label_id20, label_id200 = point_indices_from_group(
seg_indices, group, labels_pd
)
semantic_gt20[point_idx] = label_id20
semantic_gt200[point_idx] = label_id200
instance_ids[point_idx] = group["id"]
save_dict["segment20"] = semantic_gt20
save_dict["segment200"] = semantic_gt200
save_dict["instance"] = instance_ids
# ========== 修复:只保存包含有效标签的场景 ==========
# 检查该场景是否包含至少一个有效的 ScanNet20 标签
has_valid_label = np.any(semantic_gt20 != IGNORE_INDEX) if split_name != "test" else True
if has_valid_label:
# Save processed data
os.makedirs(output_path, exist_ok=True)
for key in save_dict.keys():
np.save(os.path.join(output_path, f"{key}.npy"), save_dict[key])
print(f"🎉 保存成功: {output_path}")
else:
print(f"⚠️ 跳过场景: {scene_id} - 该场景不包含任何有效的 ScanNet20 类别。")
# ===============================================
except Exception as e:
print(f"❌ 处理失败 {scene_id}: {str(e)}")
return # 即使出错也跳过,不中断整个预处理
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--dataset_root",
required=True,
help="Path to the ScanNet dataset containing scene folders",
)
parser.add_argument(
"--output_root",
required=True,
help="Output path where train/val folders will be located",
)
parser.add_argument(
"--parse_normals", default=True, type=bool, help="Whether parse point normals"
)
parser.add_argument(
"--num_workers",
default=mp.cpu_count(),
type=int,
help="Num workers for preprocessing.",
)
config = parser.parse_args()
meta_root = Path(os.path.dirname(__file__)) / "meta_data"
# Load label map
labels_pd = pd.read_csv(
meta_root / "scannetv2-labels.combined.tsv",
sep="\t",
header=0,
)
# # Load train/val splits
# with open(meta_root / "scannetv2_train.txt") as train_file:
# train_scenes = train_file.read().splitlines()
# with open(meta_root / "scannetv2_val.txt") as val_file:
# val_scenes = val_file.read().splitlines()
# with open(os.path.join(config.dataset_root, "tasks", "custom_train.txt")) as train_file:
# train_scenes = train_file.read().splitlines()
# with open(os.path.join(config.dataset_root, "tasks", "custom_val.txt")) as val_file:
# val_scenes = val_file.read().splitlines()
# ========== 修复:使用新生成的、干净的分割文件 ==========
with open(os.path.join(config.output_root, "tasks", "clean_train.txt")) as train_file:
train_scenes = train_file.read().splitlines()
with open(os.path.join(config.output_root, "tasks", "clean_val.txt")) as val_file:
val_scenes = val_file.read().splitlines()
# ===============================================
# Create output directories
train_output_dir = os.path.join(config.output_root, "train")
os.makedirs(train_output_dir, exist_ok=True)
val_output_dir = os.path.join(config.output_root, "val")
os.makedirs(val_output_dir, exist_ok=True)
test_output_dir = os.path.join(config.output_root, "test")
os.makedirs(test_output_dir, exist_ok=True)
# Load scene paths
scene_paths = sorted(glob.glob(config.dataset_root + "/scans*/scene*"))
# Preprocess data.
print("Processing scenes...")
pool = ProcessPoolExecutor(max_workers=config.num_workers)
_ = list(
pool.map(
handle_process,
scene_paths,
repeat(config.output_root),
repeat(labels_pd),
repeat(train_scenes),
repeat(val_scenes),
repeat(config.parse_normals),
)
)