c66855adfc
Sync to site1 / sync (push) Has been cancelled
- 移除 JEPA/lejepa-identifiability 子模块 gitlink - 移除 research/multiply/MultiPLY 子模块 gitlink - 删除 .gitmodules(不再有外部 URL 依赖) - 两个目录内容作为普通文件纳入主仓库追踪 - 删除各自内部 .git 目录,消除嵌套 git 仓库
495 lines
20 KiB
Python
495 lines
20 KiB
Python
import copy
|
|
import os
|
|
import librosa
|
|
import random
|
|
import numpy as np
|
|
import pandas as pd
|
|
import json
|
|
from typing import List
|
|
import itertools
|
|
from collections import defaultdict
|
|
from utils import config
|
|
import re
|
|
from tqdm import tqdm
|
|
import objaverse
|
|
from collections import defaultdict
|
|
|
|
class HM3D(object):
|
|
def __init__(self):
|
|
self.dir_path = config.HM3D_BBOX_DIR
|
|
self.augmented_dir_path = config.BBOX_WITH_ADDED_OBJECTS_DIR
|
|
self.objectfolder_dir_path = config.BBOX_WITH_ADDED_OBJECTFOLDER_DIR
|
|
self.scene2cate = dict()
|
|
self.files = sorted(x for x in os.listdir(self.dir_path) if len(self.load_room(x)) > 0)
|
|
self.augmented_files = sorted(x for x in os.listdir(self.augmented_dir_path) if len(self.load_room_with_added_objects(x)) > 0)
|
|
self.objectfolder_files = sorted(x for x in os.listdir(self.objectfolder_dir_path) if len(self.load_room_with_added_objectfolder(x)) > 0)
|
|
|
|
for i in self.files:
|
|
scene = i.split("_")[0]
|
|
if scene not in self.scene2cate:
|
|
self.scene2cate[scene] = []
|
|
|
|
bboxes = json.load(open(os.path.join(self.dir_path, i), "r"))
|
|
for b in bboxes:
|
|
self.scene2cate[scene].append(b["class_name"])
|
|
self.categories = list(sorted(set(itertools.chain.from_iterable(self.scene2cate.values()))))
|
|
self.scenes = list(sorted(set(self.scene2cate.keys())))
|
|
|
|
def load_room(self, room_json: str):
|
|
# Coordinates in (x,y,z) format
|
|
bboxes = json.load(open(os.path.join(self.dir_path, room_json), "r"))
|
|
return bboxes
|
|
|
|
def load_room_with_added_objects(self, room_json: str):
|
|
# Coordinates in (x,y,z) format
|
|
bboxes = json.load(open(os.path.join(self.augmented_dir_path, room_json), "r"))
|
|
return bboxes
|
|
|
|
def load_room_with_added_objectfolder(self, room_json: str):
|
|
# Coordinates in (x,y,z) format
|
|
bboxes = json.load(open(os.path.join(self.objectfolder_dir_path, room_json), "r"))
|
|
return bboxes
|
|
|
|
def load_scene(self, scene: str):
|
|
bboxes = []
|
|
for i in self.files:
|
|
if scene in i:
|
|
_b = self.load_room(i)
|
|
bboxes.extend(_b)
|
|
return bboxes
|
|
|
|
@staticmethod
|
|
def room_bbox(bboxes):
|
|
_min = np.round(np.min(np.array([x["bbox"][0] for x in bboxes]), axis=0), 3)
|
|
_max = np.round(np.max(np.array([x["bbox"][1] for x in bboxes]), axis=0), 3)
|
|
return list(_min), list(_max)
|
|
|
|
def scene_description(self, room_json: str, max_box=50, format="topbottom", add_id = False):
|
|
bboxes = self.load_room(room_json)
|
|
if len(bboxes) > max_box:
|
|
bboxes = random.choices(bboxes, k=max_box)
|
|
np.set_printoptions(suppress=True)
|
|
|
|
if format == "topbottom":
|
|
_min, _max = self.room_bbox(bboxes)
|
|
room_desc = f"<room>: [{_min}, {_max}]\n"
|
|
if add_id:
|
|
obj_desc = "\n".join([f'<{x["class_name"]}>({x["id"]}): {np.round(x["bbox"], 3)}'.replace("\n", ",") for x in bboxes])
|
|
else:
|
|
obj_desc = "\n".join([f'{x["class_name"]}: {np.round(x["bbox"], 3)}'.replace("\n", ",") for x in bboxes])
|
|
return room_desc + obj_desc
|
|
elif format == "center":
|
|
obj_desc = "\n".join([f'<{x["class_name"]}>({x["id"]}): {np.round((np.array(x["bbox"][0]) + np.array(x["bbox"][1])) / 2, 3)}'.replace("\n", ",") for x in bboxes])
|
|
return obj_desc
|
|
elif format == "object_name":
|
|
obj_desc = "\n".join([f'<{x["class_name"]}>({x["id"]})'.replace("\n", ",") for x in bboxes])
|
|
return obj_desc
|
|
|
|
def multi_modal_scene_description(self, room_json: str, max_box=50):
|
|
try:
|
|
bboxes = self.load_room_with_added_objects(room_json)['incremented_bboxes']
|
|
except:
|
|
bboxes = self.load_room_with_added_objectfolder(room_json)['incremented_bboxes']
|
|
bboxes = bboxes[:max_box]
|
|
np.set_printoptions(suppress=True)
|
|
|
|
final_bboxes = []
|
|
for bbox in bboxes:
|
|
if "source" in bbox and bbox["source"] == "objaverse":
|
|
bbox["class_name"] = bbox["class_name"] + "(audio, tactile)"
|
|
elif "source" in bbox and bbox["source"] == "objectfolder":
|
|
bbox["class_name"] = bbox["class_name"] + "(tapsound)"
|
|
new_bbox_format = f'<{bbox["class_name"]}>({bbox["id"]}): {np.round(bbox["bbox"], 3)}'.replace("\n", ",")
|
|
|
|
final_bboxes.append(new_bbox_format)
|
|
|
|
obj_desc = "\n".join(final_bboxes)
|
|
return obj_desc
|
|
|
|
|
|
# TODO: include evaluation set
|
|
class AudioSet(object):
|
|
def __init__(self, training_set=True):
|
|
self.dir_path = config.AUDIOSET_DIR
|
|
|
|
# Load
|
|
ontology = json.load(open(os.path.join(self.dir_path, "ontology.json"), "r"))
|
|
if training_set:
|
|
meta_file = "unbalanced_train_segments.csv"
|
|
else:
|
|
meta_file = "eval_segments.csv"
|
|
meta = pd.read_csv(os.path.join(self.dir_path, meta_file), sep=", ", engine='python', skiprows=2)
|
|
|
|
# Set selected categories
|
|
# Ontology is a graph, not a tree. query_handles is visible tree roots.
|
|
query_handles = ["Music", "Sounds of things"]
|
|
valid_cate = ["Musical instrument", "Domestic sounds, home sounds", "Liquid", "Glass", "Printer",
|
|
"Air conditioning", "Mechanical fan", "Clock", "Fire alarm", "Smoke detector, smoke alarm",
|
|
"Doorbell", "Alarm clock", "Ringtone", "Telephone bell ringing", "Domestic sounds, home sounds",
|
|
"Loudspeaker", "Radio", "Television", "MP3", "Domestic animals, pets"]
|
|
block_cate = ["Human sounds", "Vehicle"]
|
|
|
|
# Put audios on the node.
|
|
name2node = {x["name"]: x["id"] for x in ontology}
|
|
node2child = {x["id"]: x["child_ids"] for x in ontology}
|
|
valid_nodes = self.iterative_query([name2node[x] for x in valid_cate], query_dict=node2child)
|
|
block_nodes = self.iterative_query([name2node[x] for x in block_cate], query_dict=node2child)
|
|
|
|
self._node2audio = defaultdict(list)
|
|
for id, labels in zip(meta["# YTID"], meta["positive_labels"]):
|
|
labels = set(labels.strip('"').split(","))
|
|
if len(labels & block_nodes): continue
|
|
for i in (labels & valid_nodes):
|
|
self._node2audio[i].append(id)
|
|
|
|
# Pruning nodes without audios
|
|
self.nodes = list()
|
|
for i in [x["id"] for x in ontology]:
|
|
nodes = self.iterative_query([i], node2child)
|
|
if any(len(self._node2audio[x]) for x in nodes):
|
|
self.nodes.append(i)
|
|
|
|
query_nodes = self.iterative_query([name2node[x] for x in query_handles], query_dict=node2child)
|
|
self.nodes = list(set(self.nodes) & query_nodes)
|
|
|
|
filtered_ontology = [x for x in ontology if x["id"] in self.nodes]
|
|
self.node2name = {x["id"]: x["name"] for x in filtered_ontology}
|
|
self.node2description = {x["id"]: x["description"] for x in filtered_ontology}
|
|
self.node2child = {x["id"]: (set(x["child_ids"]) & set(self.nodes)) for x in filtered_ontology}
|
|
self.node2father = defaultdict(list)
|
|
for k, v in self.node2child.items():
|
|
for i in v:
|
|
self.node2father[i].append(k)
|
|
|
|
# Others
|
|
self.audio_ids = self.get_ids(self.nodes)
|
|
self.meta = meta[meta["# YTID"].isin(self.audio_ids)]
|
|
self.downloader = os.path.join(config.THIRD_PARTY_DIR, "youtube-dl")
|
|
print(f"AudioSet {meta_file}: {len(self.meta)} / {len(meta)}, cate {len(self.nodes)} / {len(ontology)}")
|
|
|
|
|
|
# Display
|
|
root_nodes = set(self.nodes).difference(set(itertools.chain.from_iterable(self.node2child.values())))
|
|
self.print_tree(root_nodes)
|
|
|
|
def get_ids(self, nodes: List[str]):
|
|
nodes = self.iterative_query(nodes, self.node2child)
|
|
return list(set(itertools.chain.from_iterable(self._node2audio[x] for x in nodes)))
|
|
|
|
def get_audio(self, audio_id):
|
|
assert audio_id in self.audio_ids # YTID is unique in training set
|
|
info = self.meta[self.meta["# YTID"] == audio_id]
|
|
assert len(info) == 1
|
|
_path = os.path.join(config.AUDIOSET_DIR, f"{audio_id}.wav")
|
|
if not os.path.exists(_path):
|
|
os.system(f"sh {os.path.join(config.THIRD_PARTY_DIR, 'fetch_audio.sh')} "
|
|
f"{audio_id} {info['start_seconds'].values[0]} {info['end_seconds'].values[0]} "
|
|
f"{_path} {self.downloader}")
|
|
|
|
audio_data = None
|
|
success = False
|
|
if os.path.exists(_path):
|
|
audio_data, _ = librosa.load(_path, sr=config.RIR_SAMPLING_RATE)
|
|
success = True
|
|
return audio_data, success
|
|
|
|
# @staticmethod
|
|
# def iterative_query(nodes: List[str], query_dict: dict[str, List[str]], include_root=True) -> set:
|
|
# q = copy.deepcopy(nodes)
|
|
# res = []
|
|
# while len(q):
|
|
# node = q.pop()
|
|
# res.append(node)
|
|
# q.extend(query_dict[node])
|
|
|
|
# if not include_root:
|
|
# for i in nodes:
|
|
# res.remove(i)
|
|
# return set(res)
|
|
|
|
def print_tree(self, nodes, max_depth=100):
|
|
q = []
|
|
for i in nodes:
|
|
q.append((i, 0))
|
|
while len(q):
|
|
node, depth = q.pop()
|
|
for i in self.node2child[node]:
|
|
q.append((i, depth+1))
|
|
if depth < max_depth:
|
|
num = len(set(itertools.chain.from_iterable(
|
|
self._node2audio[x] for x in self.iterative_query([node], self.node2child))))
|
|
print(f'{"--" * depth} {self.node2name[node]}: {num}, {self.node2description[node]}')
|
|
|
|
@property
|
|
def meta_info(self):
|
|
info = {}
|
|
for i in self.nodes:
|
|
path = self.iterative_query([i], self.node2father)
|
|
tags = [self.node2name[x] for x in path]
|
|
description = self.node2description[i]
|
|
info[self.node2name[i]] = f"tags={tags}, description='{description}'"
|
|
return info
|
|
|
|
|
|
class Objaverse(object):
|
|
def __init__(self, selected=True):
|
|
self.dir_path = config.OBJAVERSE_DIR
|
|
if not selected:
|
|
with open(os.path.join(config.OBJAVERSE_DIR, "audio_objaverse.txt"), "r") as f:
|
|
valid_cate = f.readlines()
|
|
else:
|
|
with open(os.path.join(config.OBJAVERSE_DIR, "selected_objaverse_lvis.txt"), "r") as f:
|
|
valid_cate = f.readlines()
|
|
valid_cate = [x.strip("\n") for x in valid_cate]
|
|
self.lvis = {k.strip(): v for k, v in objaverse.load_lvis_annotations().items() if k in valid_cate}
|
|
self.categories = sorted(valid_cate)
|
|
|
|
# self.meta_info = []
|
|
# for k, v in self.anns.items():
|
|
# info = {'id': k}
|
|
#
|
|
# if len(v["name"]):
|
|
# info["name"] = v["name"]
|
|
# if k in self.lvis: # Precise labels
|
|
# info["label"] = self.lvis[k]
|
|
# if len(v["categories"]):
|
|
# info["categories"] = [x['name'] for x in v['categories']]
|
|
# if len(v["tags"]):
|
|
# info["tags"] = [x['name'] for x in v['tags']]
|
|
# if len(v["description"]):
|
|
# info["description"] = v['description']
|
|
# self.meta_info.append(str(json.dumps(info)))
|
|
|
|
@staticmethod
|
|
def get_objects(uids):
|
|
return objaverse.load_objects(uids=uids, download_processes=1)
|
|
|
|
|
|
class Objaverse_Material(object):
|
|
def __init__(self):
|
|
self.dir_path = config.DATA_DIR
|
|
self.all_objaverse_materials = json.load(open(os.path.join(self.dir_path, "objaverse_random_obj_material_dict.json")))
|
|
|
|
self.all_objects = []
|
|
self.all_cats = []
|
|
|
|
idx = 0
|
|
|
|
for cat, materials in self.all_objaverse_materials.items():
|
|
for material in materials:
|
|
material['obj_id'] = idx
|
|
self.all_objects.append(material)
|
|
idx += 1
|
|
|
|
self.all_cats.append(cat)
|
|
|
|
def get_random_objs(self, num: int) -> list:
|
|
chosen_cats = np.random.choice(self.all_cats, num)
|
|
|
|
chosen_objects = []
|
|
find_ambiguous = False
|
|
|
|
for cat in chosen_cats:
|
|
chosen_objects.extend([str(obj) for obj in self.all_objaverse_materials[cat]])
|
|
if len(self.all_objaverse_materials[cat]) >= 2:
|
|
find_ambiguous = True
|
|
|
|
while not find_ambiguous:
|
|
cat = np.random.choice(self.all_cats, 1)[0]
|
|
if len(self.all_objaverse_materials[cat]) >= 2:
|
|
find_ambiguous = True
|
|
chosen_objects.extend([str(obj) for obj in self.all_objaverse_materials[cat]])
|
|
|
|
return chosen_objects
|
|
|
|
|
|
class Objaverse_Material2(object):
|
|
def __init__(self):
|
|
self.dir_path = config.DATA_DIR
|
|
self.all_objects = json.load(open(os.path.join(self.dir_path, "objaverse_random_obj_material_list_expanded.json")))
|
|
|
|
def get_random_objs(self, num: int) -> list:
|
|
index = random.randint(0, len(self.all_objects) - num)
|
|
chosen_objects = self.all_objects[index:index+num]
|
|
return chosen_objects
|
|
|
|
|
|
class ObjectFolder(object):
|
|
def __init__(self):
|
|
self.dir_path = config.OBJECTFOLDER_DIR
|
|
self.obj2cate = dict()
|
|
meta = pd.read_csv(os.path.join(self.dir_path, "objects.csv"), header=None)
|
|
abo = pd.read_csv(os.path.join(self.dir_path, "abo_classes_3d.txt"), sep=",", header=None)
|
|
cate_map = dict(zip(meta[0].astype(int), meta[1]))
|
|
abo_map = dict(zip(abo[0], abo[1]))
|
|
self.id2material = dict(zip(meta[0].astype(int), meta[3]))
|
|
|
|
self.id2cate = dict()
|
|
for k, v in cate_map.items():
|
|
if v in abo_map:
|
|
v = abo_map[v]
|
|
self.id2cate[k] = v
|
|
|
|
self.categories = list(set(self.id2cate.values()))
|
|
cate2material = {x: [] for x in self.categories}
|
|
for k, v in self.id2cate.items():
|
|
cate2material[v].append(self.id2material[k])
|
|
|
|
select_cate = []
|
|
self.cate2materialset = dict()
|
|
self.cate2materialset2 = dict()
|
|
for k, v in cate2material.items():
|
|
if len(set(v)) > 1:
|
|
self.cate2materialset[k] = list(set(v))
|
|
# print(k, set(v))
|
|
select_cate.append(k)
|
|
if len(v) > 1:
|
|
self.cate2materialset2[k] = list(set(v))
|
|
# self.cate2ids = {x: [] for x in select_cate}
|
|
self.cate2ids = defaultdict(list)
|
|
for k, v in self.id2cate.items():
|
|
# if v in select_cate:
|
|
self.cate2ids[v].append(k)
|
|
|
|
# Before call this function, please download all ObjectFolder objects in the ObjectFolder directory
|
|
|
|
def get_objects(self, category):
|
|
material = re.findall("_(Iron|Wood|Plastic|Steel|Ceramic|Polycarbonate|Glass|iron|wood|plastic|steel|ceramic|polycarbonate|glass)", category)[0]
|
|
|
|
cat = category.replace("_"+material, "").strip()
|
|
material = material.replace("(", "").replace(")", "")
|
|
|
|
ids = self.cate2ids[cat]
|
|
if not len(ids):
|
|
cat = cat.replace("_", " ")
|
|
ids = self.cate2ids[cat]
|
|
|
|
final_ids = []
|
|
|
|
for id2 in ids:
|
|
if self.id2material[id2].lower() == material.lower():
|
|
final_ids.append(id2)
|
|
|
|
id2 = random.choice(final_ids)
|
|
path = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, str(id2), "model_new.obj")
|
|
|
|
return cat, id2, material, path
|
|
|
|
@staticmethod
|
|
def modify_obj(fn, new_fn):
|
|
fin = open(fn, 'r')
|
|
fout = open(new_fn, 'w')
|
|
lines = [line.rstrip() for line in fin]
|
|
fin.close()
|
|
|
|
vertices = []; normals = []; faces = []; vns = []
|
|
header = ""
|
|
for line in lines:
|
|
if line.startswith('v '):
|
|
vertice = np.float32(line.split()[1:4])
|
|
line = "v %f %f %f"%(vertice[0], vertice[2], vertice[1])
|
|
|
|
fout.write(line+"\n")
|
|
fout.close()
|
|
|
|
@staticmethod
|
|
def normalize_pts(pts):
|
|
out = np.array(pts, dtype=np.float32)
|
|
center = np.mean(out, axis=0)
|
|
out -= center
|
|
scale = np.sqrt(np.max(np.sum(out**2, axis=1)))
|
|
out /= scale
|
|
return out
|
|
|
|
@staticmethod
|
|
def load_obj(fn):
|
|
fin = open(fn, 'r')
|
|
lines = [line.rstrip() for line in fin]
|
|
fin.close()
|
|
|
|
vertices = []; normals = []; faces = [];
|
|
for line in lines:
|
|
if line.startswith('v '):
|
|
vertices.append(np.float32(line.split()[1:4]))
|
|
elif line.startswith('f '):
|
|
faces.append(np.int32([item.split('/')[0] for item in line.split()[1:4]]))
|
|
|
|
return vertices, faces
|
|
|
|
def rotate_and_normalize(self):
|
|
for obj in tqdm(os.listdir(config.OBJECTFOLDER_OBJECTS_DIR)):
|
|
model_file = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "model.obj")
|
|
new_model_file = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "model_new.obj")
|
|
print ("processing %s"%model_file)
|
|
self.modify_obj(model_file, new_model_file)
|
|
|
|
def generate_vertices_and_forces(self):
|
|
for obj in tqdm(os.listdir(config.OBJECTFOLDER_OBJECTS_DIR)):
|
|
model_file = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "model.obj")
|
|
save_vertice_file = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "vertices.npy")
|
|
save_force_file = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "forces.npy")
|
|
v, f = self.load_obj(model_file)
|
|
v = random.sample(v, 20)
|
|
forces = np.ones((20, 3))
|
|
v = np.vstack(v)
|
|
np.save(save_vertice_file, v); np.save(save_force_file, forces)
|
|
|
|
def embed_features(self):
|
|
from msclap import CLAP
|
|
import torch
|
|
from subprocess import call
|
|
|
|
clap_model = CLAP(version = '2023', use_cuda=False)
|
|
|
|
for obj in tqdm(os.listdir(config.OBJECTFOLDER_OBJECTS_DIR)):
|
|
try:
|
|
audio_dir = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "results")
|
|
feature_save_dir = os.path.join(config.OBJECTFOLDER_OBJECTS_DIR, obj, "features")
|
|
cmd = "rm -rf %s*"%feature_save_dir
|
|
call(cmd, shell=True)
|
|
os.mkdir(feature_save_dir)
|
|
if not os.path.exists(audio_dir):
|
|
continue
|
|
|
|
audio_files = os.listdir(audio_dir)
|
|
audio_files = [os.path.join(audio_dir, file) for file in audio_files]
|
|
audio_embeddings = clap_model.get_audio_embeddings(audio_files)
|
|
|
|
for i in range(audio_embeddings.shape[0]):
|
|
torch.save(audio_embeddings[i], feature_save_dir+"/"+str(i)+".pt")
|
|
except:
|
|
print ("failed processing clap features for %s" %obj)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# TODO: spilt train and test set (use src_file label for audio files)
|
|
# # hm3d = HM3D()
|
|
# objectfolder = ObjectFolder()
|
|
# objectfolder.prepare_adapter_data()
|
|
|
|
objaverse = Objaverse()
|
|
audio_set = AudioSet(training_set=True)
|
|
audio2objaverse = json.load(open(os.path.join(config.DATA_DIR, "audio2objaverse.json"), "r"))
|
|
audio_cate2node = {v: k for k, v in audio_set.node2name.items()}
|
|
|
|
# obj_cate = random.choice(objaverse.categories)
|
|
obj2audio = dict()
|
|
for i in objaverse.categories:
|
|
audio_cate = [k for k, v in audio2objaverse.items() if i in v]
|
|
audio_ids = []
|
|
nodes = []
|
|
if len(audio_cate):
|
|
# Check audio_cate in case GPT generates category that does not exist
|
|
nodes = [audio_cate2node[x] for x in audio_cate if x in audio_set.node2name.values()]
|
|
audio_ids = audio_set.get_ids(nodes)
|
|
obj2audio[i] = audio_ids
|
|
print(i, len(audio_ids), [audio_set.node2name[x] for x in nodes])
|
|
json.dump(obj2audio, open(os.path.join(config.DATA_DIR, "obj2audio_ids.json"), "w"))
|
|
|
|
# if len(nodes):
|
|
# node = random.choice(nodes) # Tip: be careful about data balance problem. Categories of AudioSet is a tree.
|
|
# audio_ids = audio_set.get_ids([node])
|
|
# audio_id = random.choice(audio_ids)
|
|
# audio_feature, success = audio_set.get_embedding(audio_id)
|
|
# print(obj_cate, audio_set.node2name[node], audio_id, success, len(audio_feature) if audio_feature else None)
|