galleria_photo_face_rknn_op.../face_review_endpoints.md
2026-10-03 12:16:58 +02:00

14 KiB

API Face Review: riepilogo completo degli endpoint

Scopo generale

Le API Face Review gestiscono il flusso completo di revisione dei risultati del riconoscimento facciale:

  • lettura delle run HDBSCAN;
  • visualizzazione dei cluster automatici;
  • visualizzazione dei volti classificati come noise;
  • generazione protetta delle miniature dei volti;
  • creazione e rinomina delle persone;
  • assegnazione di uno o più cluster alla stessa persona;
  • esclusione di singoli volti da una persona;
  • assegnazione singola o multipla dei volti non raggruppati;
  • conservazione separata del risultato automatico e della decisione manuale.

Principio fondamentale:

cluster automatico != persona confermata

Più cluster possono essere collegati allo stesso person_id senza fondere o cancellare i cluster originali.

Prefisso e autenticazione

Tutti gli endpoint descritti usano il prefisso:

/face-review

Le route sono protette dal middleware JWT globale. Il client deve inviare:

Authorization: Bearer <token>

Il middleware JWT verifica il token e salva il payload in:

req.user

Il nome dell'utente autenticato è disponibile in:

req.user.name

Risoluzione dell'utente visualizzato

La funzione resolveFaceReviewUser(req) applica queste regole:

  • un utente normale senza parametro user vede esclusivamente i propri dati;
  • Admin può specificare ?user=Fabio per visualizzare la libreria di un altro utente;
  • un utente normale non può selezionare un altro utente;
  • cluster, volti, fotografie e persone vengono sempre verificati lato backend.

Esempi:

JWT name = Fabio
GET /face-review/context
=> target_user = Fabio

JWT name = Admin
GET /face-review/context?user=Fabio
=> target_user = Fabio

JWT name = Fabio
GET /face-review/context?user=Jessica
=> HTTP 403

Endpoint di sola lettura

GET /face-review/context

Restituisce il contesto di autenticazione e la run completata più recente dell'utente visualizzato.

Richiesta utente normale:

GET /face-review/context
Authorization: Bearer <token>

Richiesta Admin:

GET /face-review/context?user=Fabio
Authorization: Bearer <admin-token>

Risposta:

{
  "authenticated_user": "Fabio",
  "target_user": "Fabio",
  "is_admin": false,
  "can_select_user": false,
  "latest_run_id": "c44546a7-55a8-423c-b3b3-7dd5da671a1d"
}

Possibili errori:

  • 401 se il token non è valido o manca l'identità;
  • 403 se un utente normale prova a selezionare un altro utente;
  • 404 se l'utente richiesto non possiede dati riconoscibili.

GET /face-review/runs

Restituisce la cronologia delle run di clustering per l'utente visualizzato.

GET /face-review/runs
GET /face-review/runs?user=Fabio

Ogni run contiene, tra gli altri:

  • id;
  • user;
  • status;
  • scope;
  • embedding_count;
  • min_cluster_size;
  • min_samples;
  • metric;
  • selection_method;
  • engine e engine_version;
  • cluster_count;
  • clustered_faces;
  • noise_faces;
  • noise_percent;
  • date di avvio e completamento.

Risposta:

{
  "user": "Fabio",
  "runs": [
    {
      "id": "...",
      "status": "done",
      "embedding_count": 30,
      "cluster_count": 5,
      "clustered_faces": 14,
      "noise_faces": 16
    }
  ]
}

GET /face-review/clusters

Restituisce i cluster e i relativi volti. Senza run_id usa la run completata più recente.

GET /face-review/clusters
GET /face-review/clusters?run_id=<run-id>
GET /face-review/clusters?user=Fabio

Per ogni cluster restituisce:

  • identificativo e label HDBSCAN;
  • dimensione;
  • parametri della run;
  • eventuale person_id e nome della persona;
  • elenco dei volti appartenenti al cluster.

Ogni volto include:

  • id;
  • photo_id e photo_name;
  • face_index;
  • detector score;
  • probabilità HDBSCAN;
  • stato di revisione;
  • rotazione;
  • URL protetto della miniatura.

Risposta sintetica:

{
  "user": "Fabio",
  "run": {
    "id": "...",
    "status": "done"
  },
  "clusters": [
    {
      "id": "...",
      "label": 2,
      "size": 4,
      "person_id": "...",
      "person_name": "Jessica",
      "faces": []
    }
  ]
}

GET /face-review/clusters/:clusterId

Restituisce un singolo cluster, purché appartenga all'utente autorizzato.

GET /face-review/clusters/<cluster-id>

Restituisce i dati del cluster e tutti i suoi volti. Risponde 404 se il cluster non esiste o appartiene a un altro utente.

GET /face-review/unclustered

Restituisce i volti noise della run più recente che non sono ancora assegnati manualmente a una persona.

La query applica concettualmente:

m.noise_flag = 1
AND f.person_id IS NULL

Richiesta:

GET /face-review/unclustered

Quando un noise viene assegnato a una persona:

  • cluster_id resta NULL;
  • label resta -1;
  • noise_flag resta 1;
  • person_id viene valorizzato;
  • review_status diventa accepted;
  • il volto non compare più in questo endpoint.

GET /face-review/persons

Restituisce le persone dell'utente visualizzato.

GET /face-review/persons

Risposta:

{
  "user": "Fabio",
  "persons": [
    {
      "id": "...",
      "user": "Fabio",
      "name": "Jessica",
      "created_at": "...",
      "updated_at": "..."
    }
  ]
}

GET /face-review/persons/:personId

Restituisce il dettaglio di una persona e tutti i volti assegnati, inclusi quelli provenienti da cluster e quelli assegnati manualmente dal noise.

GET /face-review/persons/<person-id>

La risposta include:

  • dati della persona;
  • numero totale di volti;
  • numero di cluster collegati;
  • elenco dei volti;
  • cluster di provenienza, se presente;
  • miniatura protetta.

Un volto noise assegnato manualmente viene restituito con:

cluster_id = null
cluster_label = null

GET /face-review/faces/:faceId/thumbnail

Genera una miniatura WebP protetta del volto.

GET /face-review/faces/<encoded-face-id>/thumbnail
GET /face-review/faces/<encoded-face-id>/thumbnail?size=256

Dimensione predefinita:

192 x 192 pixel

Intervallo accettato per size:

64 <= size <= 512

Pipeline dell'immagine:

  1. recupera il volto e la fotografia filtrandoli per utente;
  2. risolve il percorso tramite resolveFilePathForEntryId();
  3. applica esclusivamente photos.rotation;
  4. non applica automaticamente l'orientamento EXIF;
  5. aggiunge un margine al bounding box;
  6. crea un crop quadrato;
  7. ridimensiona in WebP.

Header restituiti:

Content-Type: image/webp
Cache-Control: private, max-age=3600
X-Content-Type-Options: nosniff

Il frontend deve caricare la miniatura tramite fetch() con Bearer token e trasformarla in Object URL, perché un normale elemento immagine non aggiunge automaticamente l'header Authorization.

Endpoint di scrittura per le persone

POST /face-review/persons

Crea una nuova persona per l'utente visualizzato.

POST /face-review/persons
Content-Type: application/json
Authorization: Bearer <token>

Body:

{
  "name": "Jessica"
}

Comportamento:

  • normalizza spazi e nome;
  • rifiuta nomi vuoti;
  • limita il nome a 120 caratteri;
  • cerca una persona omonima senza distinguere maiuscole e minuscole;
  • se esiste già, restituisce la persona esistente;
  • altrimenti crea un UUID e inserisce il record.

Codici:

  • 201 se la persona è stata creata;
  • 200 se il nome esisteva già;
  • 400 se il nome è assente o non valido.

PATCH /face-review/persons/:personId

Rinomina una persona.

PATCH /face-review/persons/<person-id>
Content-Type: application/json
Authorization: Bearer <token>

Body:

{
  "name": "Nuovo nome"
}

Comportamento:

  • verifica che la persona appartenga all'utente;
  • normalizza e valida il nome;
  • blocca duplicati dello stesso utente;
  • aggiorna updated_at;
  • non modifica volti o cluster.

Possibili errori:

  • 400 nome non valido;
  • 404 persona non trovata;
  • 409 nome già utilizzato da un'altra persona.

DELETE /face-review/persons/:personId/faces/:faceId

Rimuove un volto da una persona senza cancellare il volto e senza cancellare il cluster automatico.

DELETE /face-review/persons/<person-id>/faces/<encoded-face-id>
Authorization: Bearer <token>

Operazioni transazionali:

  1. verifica persona, volto e utente;
  2. imposta faces.person_id = NULL;
  3. mantiene faces.cluster_id invariato;
  4. marca la membership come excluded;
  5. salva reviewed_by e reviewed_at;
  6. inserisce o aggiorna person_face_exclusions.

Un volto rimosso da una persona può essere riassociato successivamente dal cluster o dalla UI appropriata.

Endpoint di assegnazione cluster-persona

POST /face-review/clusters/:clusterId/assign-person

Assegna un cluster a una persona esistente e consente di includere o escludere singoli volti.

POST /face-review/clusters/<cluster-id>/assign-person
Content-Type: application/json
Authorization: Bearer <token>

Body:

{
  "person_id": "<person-id>",
  "included_face_ids": [
    "face:...:0",
    "face:...:0"
  ]
}

Comportamento transazionale:

  • verifica che persona e cluster appartengano allo stesso utente;
  • verifica che tutti gli ID inclusi appartengano al cluster;
  • per i volti inclusi:
    • imposta faces.person_id;
    • marca la membership come accepted;
    • rimuove eventuali esclusioni precedenti;
  • per i volti non inclusi:
    • non li assegna alla persona;
    • marca la membership come excluded;
    • registra person_face_exclusions;
  • imposta face_clusters.person_id.

Questo endpoint permette di collegare più cluster alla stessa persona. Per esempio:

Cluster 2 -> Jessica
Cluster 3 -> Jessica
Cluster 4 -> Jessica

I cluster restano distinti e conservano la cronologia HDBSCAN.

Endpoint di assegnazione dei noise

POST /face-review/faces/:faceId/assign-person

Assegna manualmente un singolo volto a una persona.

POST /face-review/faces/<encoded-face-id>/assign-person
Content-Type: application/json
Authorization: Bearer <token>

Body:

{
  "person_id": "<person-id>"
}

Comportamento:

  • verifica persona, volto e utente;
  • aggiorna faces.person_id;
  • non modifica faces.cluster_id;
  • accetta la membership più recente del volto;
  • elimina l'eventuale esclusione precedente dalla stessa persona.

Per un noise:

cluster_id resta NULL
label resta -1
noise_flag resta 1
review_status diventa accepted

POST /face-review/unclustered/assign-person

Assegna più volti noise selezionati alla stessa persona in una singola transazione.

Persona esistente:

{
  "person_id": "<person-id>",
  "face_ids": [
    "face:...:0",
    "face:...:1"
  ]
}

Nuova persona:

{
  "name": "Nuova persona",
  "face_ids": [
    "face:...:0",
    "face:...:1"
  ]
}

Controlli principali:

  • almeno un volto deve essere selezionato;
  • deve essere presente person_id oppure name;
  • usa la run completata più recente;
  • tutti i volti devono essere noise della run corrente;
  • tutti devono appartenere all'utente;
  • tutti devono avere person_id IS NULL;
  • se un solo volto non è valido, l'intera transazione viene annullata.

Per ogni volto valido:

  • faces.person_id viene impostato;
  • cluster_id resta invariato, normalmente NULL;
  • la membership passa ad accepted;
  • vengono salvati revisore e data;
  • eventuali esclusioni pregresse dalla persona vengono eliminate.

Risposta:

{
  "ok": true,
  "user": "Fabio",
  "person": {
    "id": "...",
    "name": "Jessica"
  },
  "person_created": false,
  "run_id": "...",
  "assigned_face_ids": [],
  "assigned_count": 3
}

Tabelle coinvolte

face_cluster_runs

Conserva ogni esecuzione del clustering e i relativi parametri, metriche e stato.

face_clusters

Conserva i cluster automatici. Il campo person_id collega il cluster alla persona confermata senza eliminare il cluster.

face_cluster_memberships

Conserva l'appartenenza di ogni volto a una run e a un eventuale cluster.

Campi rilevanti:

  • run_id;
  • cluster_id;
  • face_id;
  • label;
  • probability;
  • noise_flag;
  • review_status;
  • reviewed_by;
  • reviewed_at.

Stati di revisione:

suggested
accepted
excluded

faces

Conserva bounding box, embedding e stato corrente del volto.

Distinzione importante:

  • cluster_id rappresenta il raggruppamento automatico corrente;
  • person_id rappresenta la persona confermata manualmente.

persons

Conserva le persone nominate, sempre separate per utente.

person_face_exclusions

Registra che uno specifico volto non appartiene a una specifica persona. Impedisce di perdere la decisione manuale durante successive revisioni.

Regole di coerenza

Volto clusterizzato e accettato

cluster_id = valorizzato
person_id = valorizzato
noise_flag = 0
review_status = accepted

Volto noise non revisionato

cluster_id = NULL
person_id = NULL
label = -1
noise_flag = 1
review_status = suggested

Volto noise assegnato manualmente

cluster_id = NULL
person_id = valorizzato
label = -1
noise_flag = 1
review_status = accepted

Volto escluso da una persona

person_id = NULL
cluster_id = invariato
review_status = excluded
person_face_exclusions = riga presente

Transazioni e sicurezza

Tutte le operazioni di assegnazione o rimozione importanti vengono eseguite in transazione. Se una validazione o una scrittura fallisce, viene eseguito il rollback completo.

Le API non accettano percorsi filesystem dal browser. Le miniature partono dal solo faceId, recuperano la fotografia dal database e risolvono internamente il file.

Ogni operazione verifica la coerenza tra:

utente della run
utente della fotografia
utente della persona
utente autenticato o selezionato da Admin

Conversione dei segnaposto in Markdown standard

Questo file usa segnaposto intenzionali:

  • §1 al posto di intestazione livello 1;
  • §2 al posto di intestazione livello 2;
  • §3 al posto di intestazione livello 3;
  • ` al posto dell'apice rovescio inline.

Esempio di conversione con sed, usando caratteri ASCII generati dalla shell:

hash=$(printf '\043')
tick=$(printf '\140')

sed \
  -e "s/^§1 /${hash} /" \
  -e "s/^§2 /${hash}${hash} /" \
  -e "s/^§3 /${hash}${hash}${hash} /" \
  -e "s/```/${tick}${tick}${tick}/g" \
  -e "s/`/${tick}/g" \
  face_review_endpoints_summary.md \
  > face_review_endpoints_standard.md

Nota: è importante sostituire prima ``` e poi il singolo `, come mostrato nel comando.