fix: face group name read consistency, sync_file_status fix, cleanup ghost records, identity_agent replaced with face_dedup
- get_face_groups_handler: COALESCE(tp.name, tn.label) for name consistency - sync_file_status: compare JSON vs pre_chunks (not chunk table) - face consistency: compare frames.len() not total_faces - cleanup 2 ghost records with NULL file_name/file_path - replace identity_agent with face_dedup in pipeline stages - remove identity_agent_api.rs and all references - update required_processors to match actual processors - update AGENTS.md with team responsibilities - add Studio pipeline changes documentation
This commit is contained in:
+25
-26
@@ -35,7 +35,8 @@ from redis_publisher import RedisPublisher
|
||||
from qdrant_faces import push_face_embeddings_batch
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SWIFT_BIN = os.path.join(SCRIPT_DIR, "swift_processors", ".build", "release", "swift_face_pose")
|
||||
SWIFT_BIN = os.path.join(SCRIPT_DIR, "swift_processors", ".build", "release", "swift_face")
|
||||
SWIFT_BIN_DEBUG = os.path.join(SCRIPT_DIR, "swift_processors", ".build", "debug", "swift_face")
|
||||
FACENET_PATH = os.path.join(SCRIPT_DIR, "..", "models", "facenet512.mlpackage")
|
||||
|
||||
# Pose angle classification from roll/yaw
|
||||
@@ -113,33 +114,33 @@ class FaceProcessorVision:
|
||||
return None
|
||||
|
||||
def process_with_swift(self) -> Dict:
|
||||
"""Step 1: Run swift_face_pose to get bbox + pose (generates face.json + pose.json)"""
|
||||
print(f"[FACE_V2] Step 1: Vision detection (face + pose)...")
|
||||
"""Step 1: Run swift_face to get bbox (generates face_detect.json only)
|
||||
|
||||
Note: swift_face only does face detection.
|
||||
Pose and appearance expansion happen later via separate processors:
|
||||
- swift_pose_expansion reads face_traced.json (with trace_id)
|
||||
- swift_appearance_expansion reads pose.json
|
||||
"""
|
||||
print(f"[FACE_V2] Step 1: Vision detection (face only)...")
|
||||
|
||||
# Build swift_face_pose if needed
|
||||
if not os.path.exists(SWIFT_BIN):
|
||||
# Build swift_face if needed
|
||||
if not os.path.exists(SWIFT_BIN) and not os.path.exists(SWIFT_BIN_DEBUG):
|
||||
build_dir = os.path.join(SCRIPT_DIR, "swift_processors")
|
||||
print(f"[FACE_V2] Building swift_face_pose in {build_dir}...")
|
||||
print(f"[FACE_V2] Building swift_face in {build_dir}...")
|
||||
subprocess.run(
|
||||
["swift", "build", "-c", "debug", "--product", "swift_face_pose"],
|
||||
["swift", "build", "-c", "release", "--product", "swift_face"],
|
||||
cwd=build_dir, check=True
|
||||
)
|
||||
|
||||
# Determine which binary to use
|
||||
swift_bin = SWIFT_BIN if os.path.exists(SWIFT_BIN) else SWIFT_BIN_DEBUG
|
||||
|
||||
swift_face_out = self.output_path.replace(".json", "_detect.json")
|
||||
# Pose output: same directory, but replace "face" with "pose" in filename
|
||||
output_dir = os.path.dirname(self.output_path)
|
||||
output_basename = os.path.basename(self.output_path)
|
||||
pose_basename = output_basename.replace("face", "pose")
|
||||
swift_pose_out = os.path.join(output_dir, pose_basename)
|
||||
# Appearance output: same directory, but replace "face" with "appearance" in filename
|
||||
appearance_basename = output_basename.replace("face", "appearance")
|
||||
swift_appearance_out = os.path.join(output_dir, appearance_basename)
|
||||
|
||||
cmd = [
|
||||
SWIFT_BIN,
|
||||
swift_bin,
|
||||
self.video_path,
|
||||
swift_face_out,
|
||||
swift_pose_out,
|
||||
swift_appearance_out,
|
||||
"--sample-interval", str(self.sample_interval),
|
||||
]
|
||||
if self.uuid:
|
||||
@@ -169,10 +170,10 @@ class FaceProcessorVision:
|
||||
pass
|
||||
log_f.close()
|
||||
if proc.returncode != 0:
|
||||
stderr_out = proc.stderr.read()
|
||||
stderr_out = proc.stderr.read() if proc.stderr else ""
|
||||
if stderr_out:
|
||||
print(stderr_out.strip(), file=sys.stderr)
|
||||
raise RuntimeError(f"swift_face_pose exited with code {proc.returncode}")
|
||||
raise RuntimeError(f"swift_face exited with code {proc.returncode}")
|
||||
|
||||
elapsed = time.time() - t0
|
||||
print(f"[FACE_V2] Detection done in {elapsed:.1f}s")
|
||||
@@ -180,10 +181,6 @@ class FaceProcessorVision:
|
||||
with open(swift_face_out) as f:
|
||||
face_data = json.load(f)
|
||||
|
||||
# Also check if pose.json was generated (for reference)
|
||||
if os.path.exists(swift_pose_out):
|
||||
print(f"[FACE_V2] Pose file generated: {swift_pose_out}")
|
||||
|
||||
return face_data
|
||||
|
||||
def embed_and_save(self, detection_data: Dict):
|
||||
@@ -215,7 +212,7 @@ class FaceProcessorVision:
|
||||
for frame_info in frames:
|
||||
frame_num = frame_info["frame"]
|
||||
faces = []
|
||||
for face in frame_info.get("faces", []):
|
||||
for face_idx, face in enumerate(frame_info.get("faces", [])):
|
||||
bb = face["bbox"]
|
||||
x, y, w, h = bb["x"], bb["y"], bb["width"], bb["height"]
|
||||
|
||||
@@ -242,9 +239,10 @@ class FaceProcessorVision:
|
||||
if emb is not None:
|
||||
embed_count += 1
|
||||
# Collect for batch Qdrant push
|
||||
# Use face_idx to distinguish multiple faces in same frame
|
||||
all_embeddings.append({
|
||||
"frame": frame_num,
|
||||
"trace_id": 0, # Initial, updated by face_tracker
|
||||
"trace_id": face_idx, # Use face_idx as unique identifier within frame
|
||||
"bbox": {"x": x, "y": y, "width": w, "height": h},
|
||||
"confidence": face.get("confidence", 0.5),
|
||||
"embedding": emb,
|
||||
@@ -345,6 +343,7 @@ def main():
|
||||
parser.add_argument("--uuid", "-u", default="")
|
||||
parser.add_argument("--sample-interval", type=int, default=3)
|
||||
parser.add_argument("--force", action="store_true")
|
||||
parser.add_argument("--frames", type=str, default=None, help=argparse.SUPPRESS)
|
||||
args = parser.parse_args()
|
||||
|
||||
publisher = RedisPublisher(args.uuid) if args.uuid else None
|
||||
|
||||
Reference in New Issue
Block a user