Spaces:
Sleeping
Sleeping
| """ | |
| Main-figure pose extractor β gold 4x1000 subset. | |
| Tests upgrade (a) from the pose investigation: instead of averaging all | |
| skeletons in an image (extract_pose.py), store ONLY the largest detected | |
| figure's torso-normalised skeleton, plus quality indicators so garbage | |
| detections can be filtered in analysis: | |
| filename | |
| n_persons β YOLO detections in the image | |
| main_conf β box confidence of the largest figure | |
| main_kpt_conf β mean keypoint confidence of the largest figure | |
| main_torso_px β torso height in pixels (tiny => unreliable normalisation) | |
| main_area β bbox area fraction of the image | |
| main_skel_0..33 β 17 kpts x (dx,dy), torso-normalised, 0 where invisible | |
| Output: data/features/pose.parquet (checkpointed, resumable) | |
| Usage: python features/extract_pose_main_gold.py [--model yolov8m-pose.pt] | |
| """ | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| from tqdm import tqdm | |
| ROOT = Path(__file__).resolve().parent.parent | |
| IMAGES = ROOT / "data/images" | |
| SELECTED = ROOT / "data/artwork_metadata.csv" | |
| OUTPUT = ROOT / "data/features/pose.parquet" | |
| KPT_THR = 0.3 | |
| L_SHOULDER, R_SHOULDER, L_HIP, R_HIP = 5, 6, 11, 12 | |
| def normalise_skeleton(kpts, conf): | |
| """Torso-normalised skeleton (matches extract_pose.py) + torso height px.""" | |
| vis = conf >= KPT_THR | |
| anchors = [L_SHOULDER, R_SHOULDER, L_HIP, R_HIP] | |
| if sum(vis[i] for i in anchors) >= 2: | |
| mid_shoulder = (kpts[L_SHOULDER] + kpts[R_SHOULDER]) / 2.0 | |
| mid_hip = (kpts[L_HIP] + kpts[R_HIP]) / 2.0 | |
| centre = (mid_shoulder + mid_hip) / 2.0 | |
| height = float(np.linalg.norm(mid_shoulder - mid_hip)) | |
| else: | |
| visible = kpts[vis] | |
| if len(visible) == 0: | |
| return np.zeros(34, dtype=np.float32), 0.0 | |
| centre = visible.mean(axis=0) | |
| height = float(np.linalg.norm(visible.max(axis=0) - visible.min(axis=0))) | |
| norm = (kpts - centre) / max(height, 1.0) | |
| norm[~vis] = 0.0 | |
| return norm.ravel().astype(np.float32), height | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", default="yolov8m-pose.pt") | |
| ap.add_argument("--save-every", type=int, default=200) | |
| args = ap.parse_args() | |
| from ultralytics import YOLO | |
| model = YOLO(args.model) | |
| gold = (pd.read_csv(SELECTED, dtype=str) | |
| .drop_duplicates("filename")[["filename"]]) | |
| existing = (pd.read_parquet(OUTPUT) if OUTPUT.exists() | |
| else pd.DataFrame(columns=["filename"])) | |
| have = set(existing["filename"]) | |
| todo = gold[~gold["filename"].isin(have)]["filename"].tolist() | |
| print(f"gold={len(gold)} done={len(have)} todo={len(todo)}") | |
| rows, failed = [], 0 | |
| for filename in tqdm(todo): | |
| try: | |
| res = model(str(IMAGES / filename), verbose=False)[0] | |
| row = {"filename": filename, "n_persons": 0, "main_conf": 0.0, | |
| "main_kpt_conf": 0.0, "main_torso_px": 0.0, "main_area": 0.0, | |
| **{f"main_skel_{i}": 0.0 for i in range(34)}} | |
| if res.boxes is not None and len(res.boxes) > 0: | |
| areas = ((res.boxes.xyxy[:, 2] - res.boxes.xyxy[:, 0]) | |
| * (res.boxes.xyxy[:, 3] - res.boxes.xyxy[:, 1])) | |
| j = int(areas.argmax()) | |
| kpts = res.keypoints.xy[j].cpu().numpy() | |
| conf = (res.keypoints.conf[j].cpu().numpy() | |
| if res.keypoints.conf is not None else np.ones(17)) | |
| skel, torso = normalise_skeleton(kpts, conf) | |
| ih, iw = res.orig_shape | |
| row.update({"n_persons": len(res.boxes), | |
| "main_conf": float(res.boxes.conf[j]), | |
| "main_kpt_conf": float(conf.mean()), | |
| "main_torso_px": torso, | |
| "main_area": float(areas[j]) / (ih * iw)}) | |
| row.update({f"main_skel_{i}": float(v) for i, v in enumerate(skel)}) | |
| rows.append(row) | |
| except Exception as e: | |
| failed += 1 | |
| sys.stderr.write(f"FAIL {filename}: {e}\n") | |
| if len(rows) >= args.save_every: | |
| existing = pd.concat([existing, pd.DataFrame(rows)], ignore_index=True) | |
| existing.to_parquet(OUTPUT, index=False) | |
| rows = [] | |
| if rows: | |
| existing = pd.concat([existing, pd.DataFrame(rows)], ignore_index=True) | |
| existing.to_parquet(OUTPUT, index=False) | |
| print(f"Wrote {OUTPUT}: {len(existing)} rows. Failures: {failed}") | |
| if __name__ == "__main__": | |
| main() | |