Aggiunge documentazione e script per face KNN e face scan

This commit is contained in:
Fabio 2026-10-26 22:03:35 +01:00
parent 867ed8d801
commit 17b4cf6cf5
7 changed files with 462 additions and 0 deletions

162
KNN-SCAN-flusso-shadow.md Normal file
View file

@ -0,0 +1,162 @@
# Flusso KNN Shadow — progetto s190js
## Panoramica
Il confronto KNN in modalità **shadow** viene eseguito lato **server Node.js**, non nel browser. Il flusso parte dal worker che aggiorna i prototipi facciali e arriva al binario nativo `face_knn`, passando attraverso il modulo di confronto, il runtime KNN e uno script shell.
```text
Server Node.js
└── tools/runPrototypeWorker.js
└── tools/runFaceKnnShadow.js
└── tools/faceKnnRuntime.js
└── native/face-knn/run-face-knn.sh
└── native/face-knn/bin/face_knn
```
Percorso del progetto usato negli esempi:
```bash
/home/nvme/dockerdata/prove/s190js
```
## Ruolo dei componenti
### 1. `tools/runPrototypeWorker.js` — worker Node.js
È il processo server che gestisce i job di aggiornamento dei prototipi facciali.
In sintesi:
1. recupera un job in stato `pending`;
2. lo porta in elaborazione (`processing`);
3. esegue la procedura principale di selezione dei prototipi;
4. salva il risultato nel database;
5. dopo il salvataggio, avvia il confronto KNN shadow.
Il confronto shadow viene quindi eseguito **dopo** il salvataggio del risultato principale. Non è il componente che decide quali prototipi selezionare nella procedura ordinaria.
### 2. `tools/runFaceKnnShadow.js` — confronto KNN in modalità shadow
Questo modulo coordina il confronto tra i risultati KNN nativi e quelli di riferimento prodotti dall'esperimento.
Il suo compito è verificare e registrare eventuali differenze. In modalità `shadow`, il risultato del KNN nativo è usato per validazione e analisi: non sostituisce la selezione principale dei prototipi.
Eventuali errori o discrepanze del confronto vengono registrati nei log; secondo il flusso esaminato, non annullano il salvataggio dei prototipi già completato.
### 3. `tools/faceKnnRuntime.js` — runtime KNN
Questo modulo prepara e avvia l'esecuzione del KNN nativo.
In base al codice esaminato, si occupa di attività come:
- leggere la configurazione KNN;
- recuperare gli embedding delle facce e i prototipi da confrontare;
- preparare i file di input temporanei richiesti dal programma nativo;
- invocare il launcher configurato;
- leggere e interpretare l'output JSON del binario.
Il launcher è configurato nel file:
```text
native/face-knn/config.json
```
Il valore atteso per `launcher` è:
```text
native/face-knn/run-face-knn.sh
```
L'effettiva esecuzione dipende dalla configurazione: il KNN deve essere abilitato e la modalità shadow deve essere attiva.
### 4. `native/face-knn/run-face-knn.sh` — script shell
È il punto di ingresso shell usato per lanciare il programma nativo.
Lo script cambia nella propria directory e poi esegue il binario:
```text
./bin/face_knn
```
Questo passaggio permette al programma nativo di trovare correttamente i file relativi alla propria directory, come il kernel OpenCL, senza dipendere dalla directory da cui è stato avviato il server.
Lo script è quindi un **launcher lato server**. Non viene eseguito dal browser.
### 5. `native/face-knn/bin/face_knn` — binario nativo
È il programma compilato che esegue il confronto KNN.
Riceve dal runtime i percorsi dei file di input e i parametri necessari, come il numero di query, il numero di prototipi, la dimensione degli embedding, il valore di `top-k` e il backend richiesto.
Il programma restituisce i risultati al processo Node.js, che li interpreta e li usa nel rapporto di confronto shadow.
## Quando viene eseguito il flusso
Il flusso non viene eseguito semplicemente perché un utente apre una pagina web.
Viene avviato dal worker server durante l'elaborazione di un job di aggiornamento dei prototipi, dopo il completamento del salvataggio principale, se la configurazione KNN abilita il confronto shadow.
L'assegnazione di un cluster a una persona accoda un job di aggiornamento dei prototipi. Il daemon/worker elabora quel job; il confronto KNN shadow è una fase successiva del lavoro server.
## Cosa fa e cosa non fa la modalità shadow
| Attività | Componente principale |
|---|---|
| Gestire il job di aggiornamento | `runPrototypeWorker.js` |
| Selezionare e salvare i prototipi principali | Worker e procedura di selezione |
| Coordinare il confronto di validazione | `runFaceKnnShadow.js` |
| Preparare input e invocare il programma nativo | `faceKnnRuntime.js` |
| Avviare il binario | `run-face-knn.sh` |
| Eseguire il calcolo KNN nativo | `bin/face_knn` |
**Importante:** in modalità `shadow`, il KNN nativo è un controllo parallelo di validazione. Non è il motore che determina direttamente quali prototipi vengono salvati. Per cambiare questo comportamento servirebbe una modifica esplicita al flusso applicativo e una verifica separata.
## Verifiche sul server
Per controllare la configurazione effettiva e i punti di collegamento, eseguire sul server:
```bash
cd /home/nvme/dockerdata/prove/s190js
echo "=== Configurazione KNN ==="
cat native/face-knn/config.json
echo
echo "=== Collegamenti nel codice ==="
grep -n -E \
'runFaceKnnShadow|runNativeKnn|launcher|spawnSync' \
tools/runPrototypeWorker.js \
tools/runFaceKnnShadow.js \
tools/faceKnnRuntime.js
```
Per verificare che lo script shell lanci il binario previsto:
```bash
cd /home/nvme/dockerdata/prove/s190js
cat native/face-knn/run-face-knn.sh
ls -l native/face-knn/run-face-knn.sh \
native/face-knn/bin/face_knn
```
## Nota sul fallback
La configurazione può contenere un backend di fallback, ad esempio CPU. La sola presenza di un'impostazione come `fallbackBackend` non dimostra però che il codice esegua automaticamente un secondo tentativo in caso di errore. Per affermarlo con certezza occorre verificare nel runtime il percorso di gestione degli errori e l'eventuale seconda invocazione.
## Conclusione
La catena è interamente lato server:
```text
runPrototypeWorker.js
→ runFaceKnnShadow.js
→ faceKnnRuntime.js
→ run-face-knn.sh
→ bin/face_knn
```
Il worker principale salva i prototipi; successivamente, se la configurazione lo consente, viene lanciato il confronto KNN shadow per confrontare e validare i risultati. Il browser non esegue né lo script shell né il binario nativo.
Questo documento descrive il flusso ricostruito dal codice esaminato. Per confermare che una specifica esecuzione sia avvenuta, occorre controllare la configurazione attiva e i log del worker.

164
KNN-SCAN.md Normal file
View file

@ -0,0 +1,164 @@
# Gestione dei prototipi facciali: flusso di assegnazione e aggiornamento
**Progetto:** server fotografico `s190js`
**Percorso di riferimento:** `/home/nvme/dockerdata/prove/s190js`
**Scopo:** descrivere il comportamento verificato nel codice, dalla nomina di un cluster al salvataggio dei prototipi usati per il riconoscimento facciale.
> **Nota sul livello di verifica:** questo documento descrive il codice esaminato durante l'analisi. Non dimostra che uno specifico job sia stato completato nel database in produzione: per quello occorre controllare i record reali.
## 1. Panoramica
Quando si assegna una persona a un cluster dalla revisione dei volti, la route aggiorna le associazioni dei volti e richiede un aggiornamento dei prototipi per quella persona.
La richiesta viene registrata nella tabella `face_prototype_jobs`. Un daemon controlla la coda e avvia `tools/runPrototypeWorker.js` in modalità persistente (`--persist`). Il worker esegue la selezione dei prototipi e, se l'elaborazione riesce, salva i risultati in una transazione SQLite.
Il confronto KNN/OpenCL in modalità `shadow` viene eseguito **dopo** il salvataggio. Serve a confrontare i risultati e non decide quali prototipi vengono salvati.
## 2. Componenti coinvolti
| Componente | Responsabilità |
|---|---|
| `routes/faceReview.js` | Gestisce l'assegnazione della persona al cluster e richiede l'aggiornamento dei prototipi. |
| `queuePrototypeRefresh()` in `routes/faceReview.js` | Crea o aggiorna un job `pending` per la persona e l'utente. |
| Tabella `face_prototype_jobs` | Registra le richieste, lo stato, i tentativi, gli errori e il risultato. |
| `tools/runPrototypeDaemon.js` | Controlla periodicamente la coda, recupera alcuni job bloccati e avvia il worker. |
| `tools/runPrototypeWorker.js` | Calcola la selezione, salva i prototipi e le valutazioni e aggiorna lo stato del job. |
| `person_face_prototypes` | Contiene i prototipi facciali associati alle persone. |
| `person_face_recognition_evaluations` | Contiene le valutazioni di riconoscimento calcolate dal worker. |
| `face_recognition_index_state` | Indica che l'indice di riconoscimento deve essere aggiornato (`dirty`). |
| `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js` | Eseguono il confronto KNN in modalità shadow, se abilitato e configurato. |
## 3. Dall'assegnazione del cluster alla coda
Il frontend invia una richiesta alla route:
```text
POST /face-review/clusters/:clusterId/assign-person
```
con i dati della persona e gli ID dei volti inclusi. Nel codice esaminato, la route:
1. verifica che la persona e il cluster siano validi;
2. verifica che i volti indicati appartengano al cluster;
3. aggiorna le membership, segnando i volti inclusi come accettati e quelli esclusi come esclusi;
4. assegna `person_id` ai volti inclusi e rimuove eventuali esclusioni corrispondenti;
5. gestisce i volti esclusi e le relative righe in `person_face_exclusions`;
6. aggiorna `face_clusters.person_id`;
7. chiama `queuePrototypeRefresh()` con `reason: 'cluster_assigned'`.
Questi aggiornamenti e l'accodamento sono eseguiti nel contesto della transazione della route.
### Come viene accorpata la coda
`queuePrototypeRefresh()` cerca un job con lo stesso `user`, lo stesso `person_id` e `status = 'pending'`.
- **Se lo trova:** aggiorna il job esistente, compresi `reason`, `requested_by` e `requested_at`.
- **Se non lo trova:** crea un nuovo job con `status = 'pending'`.
Il riutilizzo riguarda solo i job `pending`. Se esiste già un job `processing`, questa funzione non lo aggiorna: può creare un nuovo job `pending`.
## 4. Come il daemon esegue i job
Il `package.json` definisce:
```json
"prototype:daemon": "node tools/runPrototypeDaemon.js",
"prototype:worker": "node tools/runPrototypeWorker.js --persist",
"prototype:once": "node tools/runPrototypeDaemon.js --once"
```
Il daemon:
1. conta i job con `status = 'pending'`;
2. se non ne trova, attende l'intervallo configurato e ricontrolla (salvo la modalità `--once`);
3. se ne trova, avvia `tools/runPrototypeWorker.js --persist`;
4. dopo un successo, può avviare immediatamente un altro job pending;
5. se il worker fallisce, registra l'errore nei log e attende prima di ricontrollare la coda.
### Recupero dei job bloccati
`recoverStaleJobs()` può riportare da `processing` a `pending` i job che hanno `started_at` più vecchio della soglia `staleMinutes` configurata. Il job viene contrassegnato con il motivo `stale_processing_recovered`.
Questa è una protezione per i job rimasti bloccati in elaborazione; non equivale a un nuovo tentativo automatico di tutti i job in stato `error`.
## 5. Cosa fa il worker
### 5.1 Selezione e acquisizione
`loadPendingJob()` cerca un job `pending` unito alla tabella `persons`, ordinando per `requested_at` crescente. Quando inizia il salvataggio persistente, `claimJob()` tenta di cambiare lo stato da `pending` a `processing`, incrementando `attempts` e registrando la firma degli input.
Se un altro worker ha già acquisito il job, l'aggiornamento condizionale non riesce e il worker segnala un errore.
### 5.2 Calcolo e controlli
Il worker carica i prototipi correnti, le esclusioni e una firma dei dati di input. Esegue l'esperimento di selezione e filtra i volti esclusi. Se non resta alcun prototipo selezionato, il job fallisce.
Prima di salvare, `persistResult()` ricalcola la firma degli input. Se è cambiata durante l'elaborazione:
- il job corrente diventa `superseded`;
- viene creato o riutilizzato un job sostitutivo `pending`;
- il risultato registra l'ID del job sostitutivo.
Questo evita di applicare una selezione calcolata su dati che nel frattempo sono cambiati.
### 5.3 Salvataggio dei prototipi
Quando gli input sono ancora validi, `persistResult()`:
1. carica le esclusioni di riferimento;
2. mantiene attivi i prototipi bloccati (`locked`) che non sono esclusi;
3. disattiva i prototipi non bloccati precedenti della persona;
4. inserisce o aggiorna i prototipi automatici selezionati in `person_face_prototypes`;
5. aggiorna l'ordine di selezione e i metadati;
6. ricalcola e salva le valutazioni in `person_face_recognition_evaluations`;
7. aggiorna `face_recognition_index_state` impostando `status = 'dirty'` e `dirty = 1`;
8. porta il job a `done`, registra `completed_at` e salva il riepilogo in `result_json`.
Le operazioni descritte sono eseguite dentro una singola transazione SQLite. Se la transazione fallisce, le modifiche non vengono confermate.
## 6. Errori e stato dei job
| Stato | Significato nel flusso esaminato |
|---|---|
| `pending` | Richiesta in attesa di essere elaborata. |
| `processing` | Worker ha acquisito il job e lo sta elaborando. |
| `done` | Salvataggio completato e risultato registrato. |
| `error` | Il worker ha registrato un errore in `last_error`. |
| `superseded` | Gli input sono cambiati durante il calcolo; è stata richiesta una nuova elaborazione. |
Se il worker fallisce, `markJobError()` imposta `status = 'error'`, registra `completed_at` e scrive il dettaglio dell'errore in `last_error`. Il daemon seleziona soltanto job `pending`: perciò un job `error` non viene ripreso automaticamente da quel normale ciclo di coda. Il recupero dei job `processing` scaduti è un meccanismo distinto.
## 7. KNN/OpenCL in modalità shadow
Dopo che `persistResult()` ha completato la transazione, il worker prova a eseguire `runFaceKnnShadow()`.
- Il confronto avviene dopo il salvataggio dei prototipi.
- Non modifica i prototipi, le valutazioni o lo stato del job.
- Un risultato incoerente viene riportato nei log.
- Un errore dello shadow viene intercettato e registrato nei log: non annulla il salvataggio già completato.
Pertanto, la modalità shadow è un controllo comparativo e non è il componente che decide la selezione persistita.
## 8. Come verificare un caso reale
Per concludere che l'assegnazione di un determinato cluster abbia prodotto prototipi, bisogna verificare nel database:
1. il job della persona e dell'utente, con motivo `cluster_assigned` o un motivo successivo se il job è stato accorpato;
2. lo stato finale e, in caso di errore, il contenuto di `last_error`;
3. `result_json`, in particolare `selected_count` e `selected_face_ids`;
4. le righe attive di `person_face_prototypes` per quella persona;
5. le valutazioni in `person_face_recognition_evaluations`;
6. lo stato dell'indice in `face_recognition_index_state`.
**Importante:** l'esistenza del codice di accodamento e del worker prova che il flusso è implementato, ma soltanto i dati reali del database confermano l'esito di una specifica assegnazione.
## 9. File e punti del codice esaminati
- `routes/faceReview.js`: route di assegnazione e `queuePrototypeRefresh()`.
- `tools/runPrototypeDaemon.js`: coda, ciclo del daemon e recupero dei job `processing` scaduti.
- `tools/runPrototypeWorker.js`: acquisizione del job, selezione, salvataggio transazionale, gestione degli errori e chiamata allo shadow KNN.
- `package.json`: script `prototype:daemon`, `prototype:worker` e `prototype:once`.
- `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js`: runtime e confronto KNN shadow.
---
Documento descrittivo basato sul codice ispezionato; verificare il codice e il database della versione in esecuzione prima di usarlo come specifica operativa definitiva.

View file

@ -0,0 +1,14 @@
{
"enabled": true,
"mode": "shadow",
"backend": "opencl",
"fallbackBackend": "cpu",
"dimension": 128,
"topK": 5,
"recognitionThreshold": 0.5,
"scoreTolerance": 0.00001,
"autoBackendMinimumComparisons": 100000,
"launcher": "native/face-knn/run-face-knn.sh",
"indexDirectory": "api_v1/face-recognition-index",
"runtimeDirectory": "var/face-knn"
}

View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
script_dir="$(
CDPATH= cd \
-- "$(dirname -- "$0")" \
&& pwd
)"
cd "$script_dir"
exec ./bin/face_knn "$@"

Binary file not shown.

61
sync-face-knn.sh Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd "$(dirname "$0")"
SRC="docs/face-knn-source"
DEPLOY="$SRC/deploy"
DST="native/face-knn"
# Sorgente -> destinazione
declare -A FILES=(
["bin/face_knn"]="$SRC/bin/face_knn"
["kernels/knn_float4_topk.cl"]="$SRC/kernels/knn_float4_topk.cl"
["config.json"]="$DEPLOY/config.json"
["run-face-knn.sh"]="$DEPLOY/run-face-knn.sh"
["SHA256SUMS"]="$SRC/SHA256SUMS"
)
echo "=== Verifica dei file sorgente ==="
for dest in "${!FILES[@]}"; do
src="${FILES[$dest]}"
if [[ ! -f "$src" ]]; then
echo "ERRORE: file sorgente mancante: $src" >&2
exit 1
fi
done
echo "Tutti i file sorgente sono presenti."
echo
echo "=== Copia ==="
for dest in \
"bin/face_knn" \
"kernels/knn_float4_topk.cl" \
"config.json" \
"run-face-knn.sh" \
"SHA256SUMS"
do
src="${FILES[$dest]}"
mkdir -p "$DST/$(dirname "$dest")"
cp -p "$src" "$DST/$dest"
echo "Copiato: $src -> $DST/$dest"
done
echo
echo "=== Verifica SHA256SUMS ==="
if [[ -f "$DST/SHA256SUMS" ]]; then
(
cd "$DST"
sha256sum -c SHA256SUMS
)
fi
echo
echo "=== File presenti nella destinazione ==="
find "$DST" -type f -printf '%P\n' | sort
echo
echo "Copia completata."

48
sync-face-scan-source.sh Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(pwd)"
echo "Sincronizzazione Face Scan..."
mkdir -p \
docs/face-scan-source/bin \
docs/face-scan-source/models \
docs/face-scan-source/lib
cp -f bin/face_scan \
docs/face-scan-source/bin/face_scan
cp -f models/SCRFD_500M_KPS_640.rknn \
docs/face-scan-source/models/
cp -f models/face_recognition_sface_2021dec.rknn \
docs/face-scan-source/models/
cp -f lib/librknnrt.so \
docs/face-scan-source/lib/
chmod 755 docs/face-scan-source/bin/face_scan
echo
echo "Verifica file copiati:"
echo
ls -lh docs/face-scan-source/bin/face_scan
ls -lh docs/face-scan-source/models/SCRFD_500M_KPS_640.rknn
ls -lh docs/face-scan-source/models/face_recognition_sface_2021dec.rknn
ls -lh docs/face-scan-source/lib/librknnrt.so
echo
echo "SHA256:"
echo
sha256sum \
docs/face-scan-source/bin/face_scan \
docs/face-scan-source/models/SCRFD_500M_KPS_640.rknn \
docs/face-scan-source/models/face_recognition_sface_2021dec.rknn \
docs/face-scan-source/lib/librknnrt.so
echo
echo "Sincronizzazione completata."