FaceID / scripts /make_pairs.py
Ypeng12's picture
Fix libgl1 package name for Debian Trixie/Bookworm in Dockerfile
69def8e
Raw
History Blame Contribute Delete
4.57 kB
import argparse
import os
import yaml
import numpy as np
import csv
def load_config(config_path):
with open(config_path, 'r') as f:
return yaml.safe_load(f)
def set_seed(seed):
np.random.seed(seed)
def get_images_per_identity(split_dir):
"""Returns a dict {identity: [list of relative image paths]}"""
images_dict = {}
if not os.path.exists(split_dir):
return images_dict
identities = sorted(os.listdir(split_dir))
for identity in identities:
identity_dir = os.path.join(split_dir, identity)
if os.path.isdir(identity_dir):
images = sorted(os.listdir(identity_dir))
# Store paths relative to project root for portability
images_paths = [os.path.relpath(os.path.join(identity_dir, img)) for img in images if img.endswith('.jpg')]
if images_paths:
images_dict[identity] = images_paths
return images_dict
def generate_pairs_for_split(images_dict, num_pairs, split_name, enforce_unique=False):
"""Generate num_pairs/2 positive and num_pairs/2 negative pairs."""
identities = sorted(list(images_dict.keys()))
# Positive pairs: need identities with at least 2 images
pos_identities = [id for id in identities if len(images_dict[id]) >= 2]
pairs = []
used_pos = set()
used_neg = set()
# Generate positive pairs
n_pos = num_pairs // 2
attempts = 0
while len(pairs) < n_pos and attempts < n_pos * 10:
attempts += 1
if not pos_identities:
break
identity = np.random.choice(pos_identities)
imgs = images_dict[identity]
idx1, idx2 = np.random.choice(len(imgs), 2, replace=False)
idx1_norm, idx2_norm = min(idx1, idx2), max(idx1, idx2)
pair_key = (identity, idx1_norm, idx2_norm)
if enforce_unique and pair_key in used_pos:
continue
used_pos.add(pair_key)
pairs.append({
'left_path': imgs[idx1],
'right_path': imgs[idx2],
'label': 1,
'split': split_name
})
# Generate negative pairs
n_neg = num_pairs - len(pairs)
attempts = 0
while len(pairs) < num_pairs and attempts < n_neg * 10:
attempts += 1
if len(identities) < 2:
break
id1, id2 = np.random.choice(identities, 2, replace=False)
img1 = np.random.choice(images_dict[id1])
img2 = np.random.choice(images_dict[id2])
id1_norm, id2_norm, i1, i2 = (id1, id2, img1, img2) if id1 < id2 else (id2, id1, img2, img1)
pair_key = (id1_norm, i1, id2_norm, i2)
if enforce_unique and pair_key in used_neg:
continue
used_neg.add(pair_key)
pairs.append({
'left_path': img1,
'right_path': img2,
'label': 0,
'split': split_name
})
return pairs
def main():
parser = argparse.ArgumentParser(description="Deterministic pair generation.")
parser.add_argument("--config", type=str, required=True, help="Path to pairs config.")
args = parser.parse_args()
config = load_config(args.config)
seed = config.get("seed", 42)
set_seed(seed)
data_dir = config["data_dir"]
output_dir = config["output_dir"]
os.makedirs(output_dir, exist_ok=True)
split_configs = {
'train': config.get('train_pairs', 2000),
'val': config.get('val_pairs', 500),
'test': config.get('test_pairs', 500)
}
for split_name, num_pairs in split_configs.items():
split_dir = os.path.join(data_dir, split_name)
images_dict = get_images_per_identity(split_dir)
if not images_dict:
print(f"Warning: No images found for split {split_name} in {split_dir}")
continue
enforce_unique = config.get('enforce_unique', False)
pairs = generate_pairs_for_split(images_dict, num_pairs, split_name, enforce_unique=enforce_unique)
output_file = os.path.join(output_dir, f"{split_name}.csv")
with open(output_file, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['left_path', 'right_path', 'label', 'split'])
writer.writeheader()
writer.writerows(pairs)
print(f"[{split_name}] Generated {len(pairs)} pairs -> {output_file}")
print("Pair generation complete.")
if __name__ == "__main__":
main()