Files
mule-image/backend/alembic/versions/0006_face_embeddings_512d.py
dtoro fa9b21856f feat: replace face pipeline with InsightFace, add content classifier
Face detection/recognition:
- Replace YuNet + SFace with InsightFace buffalo_l (RetinaFace + ArcFace)
- 512-d ArcFace embeddings (was 128-d SFace), migration 0006 resizes column
- Remove YOLO person-bbox workaround — RetinaFace is accurate enough
- Detection threshold 0.65 cleanly separates real faces (0.72+) from
  false positives on dogs/paintings (0.56-0.61)

Content-type classification:
- CLIP zero-shot classifier using native PyTorch text encoder + ONNX
  image encoder for high-quality text-image similarity
- Categories: photograph, screenshot, document, receipt, meme, artwork
- Writes Tag(kind=content_type) per photo via photo_tags
- Margin-based confidence: top-1 vs top-2 score difference
- New ClassifierSettings in config (enabled, min_confidence)
- Wired into vision_fanout pipeline

Tested: 6 real faces from 4 photos (zero false positives), 11/13 photos
classified (8 photograph, 2 artwork, 1 meme).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:49:02 +02:00

40 lines
1.3 KiB
Python

"""face_embeddings vector 128 -> 512
Revision ID: 0006_face_512d
Revises: 0005_face_embeddings
Create Date: 2026-04-10
Resize face_embeddings.vector from Vector(128) to Vector(512) for
ArcFace embeddings (InsightFace). Drops existing data and HNSW index,
recreates both.
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0006_face_512d"
down_revision: Union[str, None] = "0005_face_embeddings"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Drop index, truncate (old 128-d vectors are incompatible), resize
op.execute("DROP INDEX IF EXISTS ix_face_embeddings_vector_hnsw")
op.execute("DELETE FROM face_embeddings")
op.execute("ALTER TABLE face_embeddings ALTER COLUMN vector TYPE vector(512)")
op.execute("""
CREATE INDEX IF NOT EXISTS ix_face_embeddings_vector_hnsw
ON face_embeddings USING hnsw (vector vector_cosine_ops)
""")
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS ix_face_embeddings_vector_hnsw")
op.execute("DELETE FROM face_embeddings")
op.execute("ALTER TABLE face_embeddings ALTER COLUMN vector TYPE vector(128)")
op.execute("""
CREATE INDEX IF NOT EXISTS ix_face_embeddings_vector_hnsw
ON face_embeddings USING hnsw (vector vector_cosine_ops)
""")