ar07xd commited on
Commit
4a4a43d
·
verified ·
1 Parent(s): 59dd371

Sync from GitHub via hub-sync

Browse files
Files changed (2) hide show
  1. api/v1/analyze.py +52 -7
  2. 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
- """Generate the LLM summary for `resp`. Swallows provider errors gracefully."""
93
- try:
94
- payload = resp.model_dump(exclude=exclude) if exclude else resp.model_dump()
95
- return generate_llm_summary(payload=payload, record_id=str(record_id), media_kind=media_kind)
96
- except Exception as e: # noqa: BLE001
97
- logger.warning(f"LLM explainer failed for {media_kind}: {e}")
98
- return None
 
 
 
 
 
 
 
 
 
 
 
 
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
- f"High-frequency energy ratio {ratio:.3f} "
59
- + ("elevated fine-detail/compression energy; review with model score" if score > 0.4
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
- f"JPEG Q-table sums {sums}"
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
- f"Jaw-contour jitter {jitter:.4f} (normalized) "
176
- + ("inconsistent boundary blending detected" if jitter_score > 0.4
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
- f"Luminance imbalance across face quadrants {imbalance:.3f} "
208
- + ("inconsistent lighting direction" if lighting_score > 0.4
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
  )