feat: OCR independent chunks + TMDb seed with file_uuid
- Rule 1 now creates OCR-only chunks instead of merging into ASRX - generate_seed_embeddings.py supports --file-uuid parameter - get_seeds() filters by file_uuid - identity_matcher.py uses file_uuid for seed matching - Push QDRANT_API_KEY to Python subprocesses - Face clustering uses frame+bbox matching instead of face_id - Portal uses JWT authentication - FilesView filter logic fixed
This commit is contained in:
@@ -104,9 +104,14 @@ def main():
|
||||
print(f"[FACE_CLUSTER] Loading embeddings from Qdrant for {UUID}...")
|
||||
try:
|
||||
import requests
|
||||
qdrant_url = "http://localhost:6333"
|
||||
qdrant_url = os.environ.get("QDRANT_URL", "http://localhost:6333")
|
||||
qdrant_api_key = os.environ.get("QDRANT_API_KEY", "")
|
||||
collection = "_faces"
|
||||
|
||||
headers = {}
|
||||
if qdrant_api_key:
|
||||
headers["api-key"] = qdrant_api_key
|
||||
|
||||
# Query all embeddings for this file_uuid
|
||||
response = requests.post(
|
||||
f"{qdrant_url}/collections/{collection}/points/scroll",
|
||||
@@ -118,7 +123,8 @@ def main():
|
||||
},
|
||||
"limit": 10000,
|
||||
"with_vector": True
|
||||
}
|
||||
},
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
@@ -140,22 +146,57 @@ def main():
|
||||
print(f"[FACE_CLUSTER] Failed to load embeddings from Qdrant: {e}")
|
||||
embedding_map = {}
|
||||
|
||||
# Use embeddings from Qdrant or face.json
|
||||
# Use embeddings from Qdrant - match by frame + bbox
|
||||
embeddings = []
|
||||
face_refs = []
|
||||
|
||||
print(f"🔍 Collecting face embeddings for {UUID}...")
|
||||
|
||||
# Build a lookup: (frame, bbox_center) -> embedding
|
||||
# Use frame number and approximate bbox center for matching
|
||||
qdrant_by_frame = {}
|
||||
for point in points:
|
||||
payload = point.get("payload", {})
|
||||
frame = payload.get("frame")
|
||||
bbox = payload.get("bbox", {})
|
||||
vector = point.get("vector")
|
||||
if frame is not None and vector:
|
||||
# Use frame + bbox center as key
|
||||
cx = bbox.get("x", 0) + bbox.get("width", 0) // 2
|
||||
cy = bbox.get("y", 0) + bbox.get("height", 0) // 2
|
||||
key = (frame, cx, cy)
|
||||
if key not in qdrant_by_frame:
|
||||
qdrant_by_frame[key] = vector
|
||||
|
||||
print(f"[FACE_CLUSTER] Built Qdrant lookup with {len(qdrant_by_frame)} entries")
|
||||
|
||||
for frame_idx, frame_obj in enumerate(frames_list):
|
||||
frame_num = frame_obj.get("frame", frame_idx)
|
||||
faces = frame_obj.get("faces", [])
|
||||
if not faces:
|
||||
continue
|
||||
|
||||
for face_idx, face in enumerate(faces):
|
||||
face_id = face.get("face_id")
|
||||
if face_id and face_id in embedding_map:
|
||||
embeddings.append(embedding_map[face_id])
|
||||
face_refs.append({"frame_idx": frame_idx, "face_idx": face_idx, "face_id": face_id})
|
||||
x = face.get("x", 0)
|
||||
y = face.get("y", 0)
|
||||
w = face.get("width", 0)
|
||||
h = face.get("height", 0)
|
||||
cx = x + w // 2
|
||||
cy = y + h // 2
|
||||
|
||||
# Try exact match first
|
||||
key = (frame_num, cx, cy)
|
||||
if key in qdrant_by_frame:
|
||||
embeddings.append(qdrant_by_frame[key])
|
||||
face_refs.append({"frame_idx": frame_idx, "face_idx": face_idx})
|
||||
continue
|
||||
|
||||
# Try approximate match (within 50 pixels)
|
||||
for (qf, qx, qy), vec in qdrant_by_frame.items():
|
||||
if qf == frame_num and abs(qx - cx) < 50 and abs(qy - cy) < 50:
|
||||
embeddings.append(vec)
|
||||
face_refs.append({"frame_idx": frame_idx, "face_idx": face_idx})
|
||||
break
|
||||
|
||||
if not embeddings:
|
||||
print("❌ No embeddings found in Qdrant.")
|
||||
|
||||
Reference in New Issue
Block a user