Spaces:
Sleeping
Sleeping
Sync from GitHub via hub-sync
Browse files- api/v1/analyze.py +52 -7
- services/artifact_detector.py +7 -11
api/v1/analyze.py
CHANGED
|
@@ -89,14 +89,59 @@ def _resolve_language_hint(text: str, language_hint: str | None) -> str:
|
|
| 89 |
|
| 90 |
|
| 91 |
def _compute_llm_summary(resp, *, record_id: int, user, media_kind: str, exclude: dict | None = None):
|
| 92 |
-
"""
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
def _persist_response_payload(db: Session, record: AnalysisRecord, resp) -> None:
|
| 102 |
"""Keep reloaded/history responses aligned with the fresh API response."""
|
|
|
|
| 89 |
|
| 90 |
|
| 91 |
def _compute_llm_summary(resp, *, record_id: int, user, media_kind: str, exclude: dict | None = None):
|
| 92 |
+
"""(Disabled) Sync LLM generation is disabled. See /analyze/{record_id}/llm"""
|
| 93 |
+
return None
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
@router.post("/{record_id}/llm")
|
| 97 |
+
@limiter.limit(AUTH_ANALYZE, exempt_when=is_anon)
|
| 98 |
+
def generate_llm_endpoint(
|
| 99 |
+
request: Request,
|
| 100 |
+
record_id: int,
|
| 101 |
+
db: Session = Depends(get_db),
|
| 102 |
+
user: User | None = Depends(optional_current_user),
|
| 103 |
+
):
|
| 104 |
+
record = db.query(AnalysisRecord).filter(AnalysisRecord.id == record_id).first()
|
| 105 |
+
if not record:
|
| 106 |
+
raise HTTPException(status_code=404, detail="Analysis not found")
|
| 107 |
+
|
| 108 |
+
if user is None or record.user_id != user.id:
|
| 109 |
+
if record.user_id is not None:
|
| 110 |
+
raise HTTPException(status_code=403, detail="Forbidden")
|
| 111 |
|
| 112 |
+
payload = json.loads(record.result_json)
|
| 113 |
+
|
| 114 |
+
if "explainability" not in payload:
|
| 115 |
+
payload["explainability"] = {}
|
| 116 |
+
|
| 117 |
+
if payload["explainability"].get("llm_summary"):
|
| 118 |
+
return {"llm_summary": payload["explainability"]["llm_summary"]}
|
| 119 |
+
|
| 120 |
+
media_type = payload.get("media_type", "media")
|
| 121 |
+
|
| 122 |
+
def _strip_base64(d):
|
| 123 |
+
if isinstance(d, list): return [_strip_base64(x) for x in d]
|
| 124 |
+
if not isinstance(d, dict): return d
|
| 125 |
+
out = {}
|
| 126 |
+
for k, v in d.items():
|
| 127 |
+
if str(k).endswith("_base64"): continue
|
| 128 |
+
out[k] = _strip_base64(v)
|
| 129 |
+
return out
|
| 130 |
+
|
| 131 |
+
safe_payload = _strip_base64(payload)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
summary_obj = generate_llm_summary(payload=safe_payload, record_id=str(record.id), media_kind=media_type)
|
| 135 |
+
summary_dict = summary_obj.model_dump() if hasattr(summary_obj, "model_dump") else summary_obj
|
| 136 |
+
|
| 137 |
+
payload["explainability"]["llm_summary"] = summary_dict
|
| 138 |
+
record.result_json = json.dumps(payload)
|
| 139 |
+
db.commit()
|
| 140 |
+
|
| 141 |
+
return {"llm_summary": summary_dict}
|
| 142 |
+
except Exception as e:
|
| 143 |
+
logger.error(f"LLM generation failed: {e}")
|
| 144 |
+
raise HTTPException(status_code=500, detail="LLM generation failed")
|
| 145 |
|
| 146 |
def _persist_response_payload(db: Session, record: AnalysisRecord, resp) -> None:
|
| 147 |
"""Keep reloaded/history responses aligned with the fresh API response."""
|
services/artifact_detector.py
CHANGED
|
@@ -55,9 +55,8 @@ def detect_gan_hf_artifact(pil_img: Image.Image) -> ArtifactIndicator | None:
|
|
| 55 |
type="gan_artifact",
|
| 56 |
severity=sev,
|
| 57 |
description=(
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
else "within expected range for a natural photo")
|
| 61 |
),
|
| 62 |
confidence=float(score),
|
| 63 |
)
|
|
@@ -119,8 +118,7 @@ def detect_compression_anomaly(raw_bytes: bytes) -> ArtifactIndicator | None:
|
|
| 119 |
score = max(0.0, min(1.0, suspicious))
|
| 120 |
sev = _severity_from_score(score)
|
| 121 |
desc = (
|
| 122 |
-
|
| 123 |
-
+ (f"; {', '.join(reasons)}" if reasons else "; within typical camera range")
|
| 124 |
)
|
| 125 |
return ArtifactIndicator(
|
| 126 |
type="compression",
|
|
@@ -172,9 +170,8 @@ def detect_face_based_artifacts(pil_img: Image.Image) -> List[ArtifactIndicator]
|
|
| 172 |
type="facial_boundary",
|
| 173 |
severity=_severity_from_score(jitter_score),
|
| 174 |
description=(
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
else "face boundary appears smooth")
|
| 178 |
),
|
| 179 |
confidence=float(jitter_score),
|
| 180 |
)
|
|
@@ -204,9 +201,8 @@ def detect_face_based_artifacts(pil_img: Image.Image) -> List[ArtifactIndicator]
|
|
| 204 |
type="lighting",
|
| 205 |
severity=_severity_from_score(lighting_score),
|
| 206 |
description=(
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
else "lighting appears uniform")
|
| 210 |
),
|
| 211 |
confidence=float(lighting_score),
|
| 212 |
)
|
|
|
|
| 55 |
type="gan_artifact",
|
| 56 |
severity=sev,
|
| 57 |
description=(
|
| 58 |
+
"Unnatural pixel noise detected (common in AI-generated images)" if score > 0.4
|
| 59 |
+
else "Pixel noise is within expected range for a natural photo"
|
|
|
|
| 60 |
),
|
| 61 |
confidence=float(score),
|
| 62 |
)
|
|
|
|
| 118 |
score = max(0.0, min(1.0, suspicious))
|
| 119 |
sev = _severity_from_score(score)
|
| 120 |
desc = (
|
| 121 |
+
"Signs of repeated image compression or manipulation detected" if reasons else "No unusual compression artifacts detected"
|
|
|
|
| 122 |
)
|
| 123 |
return ArtifactIndicator(
|
| 124 |
type="compression",
|
|
|
|
| 170 |
type="facial_boundary",
|
| 171 |
severity=_severity_from_score(jitter_score),
|
| 172 |
description=(
|
| 173 |
+
"Inconsistent blending detected around the facial boundary" if jitter_score > 0.4
|
| 174 |
+
else "Facial boundary blending appears smooth and natural"
|
|
|
|
| 175 |
),
|
| 176 |
confidence=float(jitter_score),
|
| 177 |
)
|
|
|
|
| 201 |
type="lighting",
|
| 202 |
severity=_severity_from_score(lighting_score),
|
| 203 |
description=(
|
| 204 |
+
"Unnatural or inconsistent lighting detected across the face" if lighting_score > 0.4
|
| 205 |
+
else "Facial lighting appears natural and uniform"
|
|
|
|
| 206 |
),
|
| 207 |
confidence=float(lighting_score),
|
| 208 |
)
|