feat(regions): image_region storage + service for the crop pipeline (#114 slice 2)
The storage backbone both crop jobs write to and read from. image_region =
normalized bbox (rx/ry/rw/rh) + kind ('face'/'figure' → CCIP character id;
'concept' → SigLIP head bag) + the crop's embedding (nullable Vector(768) CCIP /
Vector(1152) SigLIP, one per kind) + version stamps for compute-once gating. The
bbox doubles as grounded-tag provenance. Migration 0061.
RegionService.replace_regions (scoped BY KIND so the figure + concept pipelines
don't clobber each other) + get_regions — the GPU agent's results endpoint will
call the writer; the character matcher + bag scorer read. Server-side, no GPU.
Tests: replace/get round-trip, kind-scoped replacement, CCIP vector round-trip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""image_region: detected/proposed regions + their crop embeddings (#114)
|
||||
|
||||
Storage backbone of the crop pipeline. A region = normalized bbox + the crop's
|
||||
embedding (CCIP for face/figure → character id; SigLIP for concept regions →
|
||||
head bag-of-embeddings). Also serves as grounded-tag bbox provenance.
|
||||
|
||||
Revision ID: 0061
|
||||
Revises: 0060
|
||||
Create Date: 2026-06-29
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from pgvector.sqlalchemy import Vector
|
||||
|
||||
revision: str = "0061"
|
||||
down_revision: Union[str, None] = "0060"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
_CCIP_DIM = 768
|
||||
_SIGLIP_DIM = 1152
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"image_region",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column(
|
||||
"image_record_id", sa.Integer(),
|
||||
sa.ForeignKey("image_record.id", ondelete="CASCADE"), nullable=False,
|
||||
),
|
||||
sa.Column("kind", sa.String(length=16), nullable=False),
|
||||
sa.Column("rx", sa.Float(), nullable=False),
|
||||
sa.Column("ry", sa.Float(), nullable=False),
|
||||
sa.Column("rw", sa.Float(), nullable=False),
|
||||
sa.Column("rh", sa.Float(), nullable=False),
|
||||
sa.Column("score", sa.Float(), nullable=True),
|
||||
sa.Column("detector_version", sa.String(length=64), nullable=True),
|
||||
sa.Column("crop_version", sa.String(length=64), nullable=True),
|
||||
sa.Column("embedding_version", sa.String(length=128), nullable=True),
|
||||
sa.Column("ccip_embedding", Vector(_CCIP_DIM), nullable=True),
|
||||
sa.Column("siglip_embedding", Vector(_SIGLIP_DIM), nullable=True),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_image_region_image_record_id", "image_region", ["image_record_id"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_image_region_image_record_id", table_name="image_region")
|
||||
op.drop_table("image_region")
|
||||
Reference in New Issue
Block a user