Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
235
ai_platform/modules/forensic_features/docs/ARCHITECTURE.md
Normal file
235
ai_platform/modules/forensic_features/docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
# Arhitectură
|
||||
|
||||
Document care explică **cum funcționează fiecare strat** și **de ce e separat
|
||||
așa**. Citire necesară înainte de a modifica codul.
|
||||
|
||||
## Filozofia: extracție de features, NU clasificare
|
||||
|
||||
Sistemul are **un singur scop**: să producă pentru LLM-ul tău extern un set
|
||||
de **măsurători numerice + hărți vizuale** pe care LLM-ul **nu le poate
|
||||
calcula din imagine**.
|
||||
|
||||
LLM-ul tău face deja:
|
||||
- Vision generală pe imaginea originală
|
||||
- OCR
|
||||
- Recunoaștere de tipologii custom
|
||||
- Raționament semantic
|
||||
|
||||
Acest serviciu adaugă:
|
||||
- Puls cardiac din variația de culoare facială (rPPG)
|
||||
- Drift lip-sync în milisecunde
|
||||
- Direcția luminii estimată via shape-from-shading
|
||||
- Hartă PNG cu zonele suspect de blending
|
||||
- Scoruri statistice care diferențiază imagini AI vs naturale
|
||||
|
||||
LLM-ul integrează totul și decide singur. **Noi nu decidem nimic.**
|
||||
|
||||
## Cele 4 straturi
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STRAT 4: API HTTP (api.py) │
|
||||
│ - Multipart upload │
|
||||
│ - Routing: /api/forensic-evidence, /forensic-modules etc. │
|
||||
│ - Sync sau async mode (job store in-memory) │
|
||||
└────────────────────────────┬────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STRAT 3: Glue Layer (forensic/) │
|
||||
│ │
|
||||
│ orchestrator.py — rulează modulele cu input corect │
|
||||
│ - adaptive every_n_frames │
|
||||
│ - auto-skip m26 dacă lipsește audio │
|
||||
│ - captură excepții per modul │
|
||||
│ │
|
||||
│ scoring.py — fuziune ponderată Dempster-Shafer │
|
||||
│ - score per modul × confidence × weight │
|
||||
│ - penalizare disagreement │
|
||||
│ │
|
||||
│ prompt_builder.py — formatare evidence_text │
|
||||
│ + encoding base64 PNG pentru LLM │
|
||||
└────────────────────────────┬────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STRAT 2: Detectoare Forensice (tools/m25-m29) │
|
||||
│ │
|
||||
│ Fiecare modul: │
|
||||
│ - Primește frame_paths (sau video_path pentru m26) │
|
||||
│ - Aplică algoritmul propriu (POS rPPG, NPR, etc.) │
|
||||
│ - Salvează PNG-uri pentru consum vizual LLM │
|
||||
│ - Returnează schema CONTRACT.md uniformă │
|
||||
│ │
|
||||
│ _contract.py — helper make_response() / empty_response()│
|
||||
└────────────────────────────┬────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STRAT 1: Infrastructure (preprocessing.py + libraries) │
|
||||
│ - ffmpeg pentru extracție frame-uri și audio │
|
||||
│ - MediaPipe FaceLandmarker (478 landmarks) │
|
||||
│ - OpenCV pentru imread/imwrite + image ops │
|
||||
│ - scipy/numpy pentru DSP │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Fluxul unui request
|
||||
|
||||
Pentru `POST /api/forensic-evidence` cu un video:
|
||||
|
||||
### 1. Upload + validare (api.py:106-180)
|
||||
- Multipart streaming în chunks 64KB (nu buffer tot fișierul)
|
||||
- Validare extensie: mp4/mov/avi/mkv/webm/jpg/png
|
||||
- Asignare `job_id` UUID hex 16 chars
|
||||
- Salvare temporară la `/app/data/inference/{job_id}/`
|
||||
|
||||
### 2. Orchestrator setup (forensic/orchestrator.py:75-110)
|
||||
- **Adaptive `every_n_frames`** bazat pe durata video:
|
||||
- < 5s → every_n=1 (toate cadrele)
|
||||
- < 30s → every_n=3 (~10 fps efectiv)
|
||||
- < 120s → every_n=10 (~3 fps)
|
||||
- else → every_n=30 (~1 fps)
|
||||
- **Auto-skip m26** dacă videoul nu are pistă audio
|
||||
- Extracție cadre cu ffmpeg via `preprocessing.extract_frames()`
|
||||
- Scriere `_meta.json` cu fps efectiv (m25 îl folosește pentru rPPG)
|
||||
|
||||
### 3. Rulare module (forensic/orchestrator.py:120-145)
|
||||
Fiecare modul rulează **secvențial** (default), izolat în try/except:
|
||||
- Crash într-un modul NU oprește restul
|
||||
- Modulul eșuat returnează `empty_response()` cu primary_score=None
|
||||
- Toate cele 5 module returnează aceeași schemă (CONTRACT.md)
|
||||
|
||||
### 4. Fuziune (forensic/scoring.py)
|
||||
Dempster-Shafer-inspired weighted average:
|
||||
```
|
||||
weighted_score = Σ(score_i × confidence_i × weight_i) / Σ(confidence_i × weight_i)
|
||||
```
|
||||
- Module cu `confidence=0` (NO_SIGNAL) sunt ignorate
|
||||
- Module cu confidence mic contribuie mai puțin
|
||||
- Disagreement (varianța scorurilor) penalizează confidence final
|
||||
- Weight default per modul: m28=1.3, m25=1.2, m27=1.0, m26=0.9, m29=0.8
|
||||
|
||||
Rezultatul fuziunii e **opțional** — îl includem pentru context, dar LLM-ul
|
||||
poate să-l ignore.
|
||||
|
||||
### 5. Format pentru LLM (forensic/prompt_builder.py)
|
||||
Construiește:
|
||||
- **evidence_text** — bloc text formatat, ~2-3 KB tipic
|
||||
- **images** — lista cu metadata + opțional data URLs base64
|
||||
- **summary** — obiect cu scoruri agregate pentru parsing programatic
|
||||
- **instruction_for_llm** — text fix care explică LLM-ului cum să folosească datele
|
||||
|
||||
### 6. Răspuns (api.py:225-265)
|
||||
- Sync (default): JSON imediat
|
||||
- Async (`async_mode=1`): 202 cu job_id, polling pe `/api/status/{id}`
|
||||
|
||||
## Schema unificată
|
||||
|
||||
Toate cele 5 module returnează **exact aceleași chei top-level**, garantat de
|
||||
`tools/_contract.py:make_response()`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"tool": {"id": "m25", "name": "...", "version": "1.0", "input_type": "..."},
|
||||
"summary": {
|
||||
"primary_score": 0..1 | None,
|
||||
"primary_label": "FAKE"|"REAL"|"INCERT"|"NO_SIGNAL",
|
||||
"confidence": 0..1,
|
||||
"evidence": [...],
|
||||
"frames_analyzed": int,
|
||||
"frames_with_signal": int,
|
||||
// câmpuri specifice tool — vezi MODULES.md
|
||||
},
|
||||
"per_frame": [...],
|
||||
"metrics": {...},
|
||||
"artifacts": {"images": [str]},
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"execution_time_ms": float
|
||||
}
|
||||
```
|
||||
|
||||
Asta înseamnă că:
|
||||
- Un client nou care vrea să consume un singur modul îl poate parsa cu același cod
|
||||
- Orchestrator-ul nu trebuie să cunoască câmpurile specifice fiecărui modul
|
||||
- Modulele noi pot fi adăugate fără să schimbi nimic în glue layer
|
||||
|
||||
Vezi [CONTRACT.md](CONTRACT.md) pentru schema completă.
|
||||
|
||||
## Module dependențe
|
||||
|
||||
| Modul | Cere MediaPipe? | Cere audio? | Cere ≥N cadre |
|
||||
|-------|-----------------|-------------|---------------|
|
||||
| m25 Physiology | DA (FaceLandmarker, 478 lm) | NU | ≥5 cadre, fps≥4 pt rPPG |
|
||||
| m26 Audio | DA (pentru mouth aperture) | DA | ≥10 cadre + audio |
|
||||
| m27 AI Detector | NU | NU | ≥1 cadru |
|
||||
| m28 Forgery Heatmap | DA (face mask + landmarks) | NU | ≥1 cadru cu față |
|
||||
| m29 Lighting | DA (face mask + landmarks) | NU | ≥1 cadru cu față + scene highlights |
|
||||
|
||||
`orchestrator.py` auto-detectează aceste condiții:
|
||||
- Dacă nu există audio → m26 e omis (auto_skipped)
|
||||
- Dacă MediaPipe ratează față → modul returnează `empty_response("face not detected")`
|
||||
- Dacă cadrele sunt prea puține pentru rPPG → m25 returnează NO_SIGNAL pe pulse
|
||||
|
||||
## Decizii arhitecturale importante
|
||||
|
||||
### De ce orchestrator secvențial, nu paralel?
|
||||
|
||||
Default `use_parallel=False`. Motiv: MediaPipe **nu e thread-safe la load**
|
||||
(crearea instanței FaceLandmarker face I/O + alocare GPU buffers). Doi
|
||||
workers care încarcă concomitent pot avea race condition.
|
||||
|
||||
Pe viitor, paralelizare prin **proces dedicate per modul**, nu thread.
|
||||
|
||||
### De ce job store in-memory?
|
||||
|
||||
Simplitate pentru baseline. Pentru producție serioasă, înlocuiește dict-ul
|
||||
`_jobs` din `api.py` cu Redis sau SQLite (vezi nota din docs/API.md
|
||||
secțiunea Async mode).
|
||||
|
||||
### De ce nu un model neural unic?
|
||||
|
||||
Trei motive:
|
||||
1. **Interpretabilitate**: LLM-ul vede 5 semnale separate, poate combina cu vision
|
||||
2. **Robustețe**: când un modul eșuează (ex. fără audio), restul funcționează
|
||||
3. **Cost**: rulare CPU, fără GPU obligatoriu
|
||||
|
||||
Pentru clasificare end-to-end SOTA pe GPU, folosește un model dedicat ca
|
||||
**Face X-ray** sau **UnivFD** — nu acest sistem.
|
||||
|
||||
### De ce HF detector e opt-in dezactivat?
|
||||
|
||||
Testat empiric pe 30 samples reale:
|
||||
- Cu HF agresiv (weight 0.75): regresie de la 27% strict → 20%
|
||||
- Cu HF defensiv (doar la confidence extremă): 13% strict
|
||||
- Fără HF: 27% strict, 77% lenient (cel mai bun)
|
||||
|
||||
Cauza: `Organika/sdxl-detector` e antrenat pe imagini SD vs photos. Pe video
|
||||
TikTok/news (distribuția ta), produce fals-pozitive masive. Cod prezent în
|
||||
m27 pentru când se găsește un model potrivit; activează cu `M27_USE_HF=1`.
|
||||
|
||||
## Performanță
|
||||
|
||||
| Tip input | Procesare tipică |
|
||||
|-----------|------------------|
|
||||
| Imagine statică (1-3 cadre) | 1-3 s |
|
||||
| Video scurt (<5s) cu față clară | 5-15 s |
|
||||
| Video mediu (30s) | 30-60 s |
|
||||
| Video lung (>2 min) | 1-5 min |
|
||||
|
||||
Bottleneck-uri:
|
||||
1. **MediaPipe FaceLandmarker** pe CPU: ~80-150 ms/frame
|
||||
2. **m28 forgery heatmap**: 3 component maps × Gaussian blur — heavy
|
||||
3. **rPPG POS**: rapid (~50ms/clip) pe semnal mic
|
||||
|
||||
## Următorii pași dacă vrei extindere
|
||||
|
||||
Vezi [MODULES.md](MODULES.md) pentru cum să adaugi un modul nou.
|
||||
|
||||
Pe scurt:
|
||||
1. Creează `tools/mXX_nume/nume.py` + `nume.json` config
|
||||
2. Funcția `run()` returnează schema CONTRACT prin `make_response()`
|
||||
3. Adaugă `(import_path, fn_name, input_type)` în `AVAILABLE_MODULES` din
|
||||
`forensic/orchestrator.py`
|
||||
4. Adaugă weight default în `DEFAULT_WEIGHTS` din `forensic/scoring.py`
|
||||
5. Rebuild + test cu `/api/forensic-modules` — apare în catalog
|
||||
|
||||
Nu trebuie modificat `api.py` — orchestrator-ul descoperă modulele automat.
|
||||
Loading…
Add table
Add a link
Reference in a new issue