face-intel / api /routes /analysis.py
Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
7.57 kB
"""Analysis routes — image_analysis / metadata / forensics / ocr / objects / scene / nsfw / ai_detection / embedding / forensic_metadata."""
from __future__ import annotations
from typing import List, Optional
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from api.deps import (
get_analysis_service,
get_forensic_metadata_service,
get_object_intelligence_service,
get_location_intelligence_service,
get_correlation_engine_service,
)
from models.jobs import JobKind, JobRequest
from services.analysis_service import AnalysisService
from services.forensic_metadata_service import ForensicMetadataService
from services.object_intelligence_service import ObjectIntelligenceService
from services.location_intelligence_service import LocationIntelligenceService
from services.correlation_engine_service import CorrelationEngineService
router = APIRouter()
class AnalysisRequest(BaseModel):
image_url: Optional[str] = None
image_base64: Optional[str] = None
providers: List[str] = []
class LocationRequest(BaseModel):
image_url: Optional[str] = None
image_base64: Optional[str] = None
providers: List[str] = []
# Optional pre-computed evidence (caller can pass OCR text, plate text, etc.)
ocr_text: Optional[str] = None
plate_text: Optional[str] = None
scene_label: Optional[str] = None
objects: Optional[List[dict]] = None
@router.post("/image")
async def analyze_image(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run image-quality + properties analysis."""
job_req = JobRequest(
kind=JobKind.IMAGE_ANALYSIS,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/metadata")
async def extract_metadata(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Extract EXIF / XMP / IPTC metadata."""
job_req = JobRequest(
kind=JobKind.METADATA,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/forensics")
async def forensics_analysis(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run forensics: integrity, duplicate detection, ELA, manipulation analysis."""
job_req = JobRequest(
kind=JobKind.FORENSICS,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/ocr")
async def ocr_analysis(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run OCR text extraction."""
job_req = JobRequest(
kind=JobKind.OCR,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/objects")
async def object_detection(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run object detection (YOLOv8)."""
job_req = JobRequest(
kind=JobKind.OBJECT_DETECTION,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/scene")
async def scene_classification(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run scene classification (Places365)."""
job_req = JobRequest(
kind=JobKind.SCENE_RECOGNITION,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/nsfw")
async def nsfw_detection(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run NSFW detection (NudeNet)."""
job_req = JobRequest(
kind=JobKind.NSFW_DETECTION,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/ai-detection")
async def ai_image_detection(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Run AI-generated image detection (LAID)."""
job_req = JobRequest(
kind=JobKind.AI_IMAGE_DETECTION,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/embedding")
async def image_embedding(req: AnalysisRequest,
svc: AnalysisService = Depends(get_analysis_service)):
"""Generate image embedding (EfficientNet-Lite)."""
job_req = JobRequest(
kind=JobKind.EMBEDDING,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/forensic-metadata")
async def forensic_metadata(req: AnalysisRequest,
svc: ForensicMetadataService = Depends(get_forensic_metadata_service)):
"""Extract comprehensive forensic metadata: EXIF, GPS, ICC, thumbnail,
editing history, camera fingerprint, compression analysis."""
job_req = JobRequest(
kind=JobKind.METADATA,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/object-intelligence")
async def object_intelligence(
req: AnalysisRequest,
svc: ObjectIntelligenceService = Depends(get_object_intelligence_service),
):
"""Run object intelligence: YOLO + QR codes + barcodes with crops + classification."""
job_req = JobRequest(
kind=JobKind.OBJECT_DETECTION,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
)
return await svc.analyze(job_req)
@router.post("/location")
async def location_intelligence(
req: LocationRequest,
svc: LocationIntelligenceService = Depends(get_location_intelligence_service),
):
"""Estimate image capture location from GPS + OCR + language + scene + objects."""
options: dict = {}
if req.ocr_text:
options["ocr_text"] = req.ocr_text
if req.plate_text:
options["plate_text"] = req.plate_text
if req.scene_label:
options["scene_label"] = req.scene_label
if req.objects:
options["objects"] = req.objects
job_req = JobRequest(
kind=None,
image_url=req.image_url,
image_base64=req.image_base64,
providers=req.providers,
options=options,
)
return await svc.analyze(job_req)
class CorrelateRequest(BaseModel):
"""A pre-computed UnifiedFaceReport to correlate."""
report: dict
@router.post("/correlate")
async def correlate(
req: CorrelateRequest,
svc: CorrelationEngineService = Depends(get_correlation_engine_service),
):
"""Correlate evidence in a pre-computed report into a relationship graph.
Accepts a UnifiedFaceReport (the output of any analysis job) and
builds a deterministic correlation graph of relationships between
faces, objects, locations, metadata, hashes, and embeddings.
"""
return await svc.correlate(req.report)