Salta ai contenuti

Esportare i dati

Gli amministratori del progetto esportano una parte scelta dei dati del proprio progetto come esportazione: un job che procede da solo, confeziona i dati come zip in chiaro e li tiene dietro un link fino alla scadenza. Solo gli amministratori vedono la pagina Esportazione; annotatori e osservatori non possono esportare.

I dati sono cifrati nei browser dei membri, e il server non può leggerli. Un’esportazione è l’unico punto in cui escono da questa cifratura: un worker di esportazione, un programma di cui il progetto si fida, decifra esattamente ciò che l’amministratore ha scelto e scrive l’archivio.

Un progetto registra una sola volta, nella sua pagina Esportazione, il worker di esportazione di cui si fida. Ce ne sono di due tipi, e la scelta decide chi può vedere i dati:

  • Il worker di esportazione di questo server è gestito dal gestore di questo server ed è attivo per impostazione predefinita. Il gestore del server può vedere i dati mentre vengono confezionati, e l’archivio è conservato su questo server, non cifrato, fino alla scadenza. Per registrarlo basta un clic.
  • Un worker gestito dalla tua istituzione, su una sua macchina: solo la tua istituzione può vedere i dati mentre vengono confezionati, e l’archivio è conservato nello storage del worker stesso (oppure, se la tua istituzione registra un descrittore che lo consente, su questo server). Configurazione: installa l’SDK dei processori, genera le chiavi e registrale (il README dell’SDK, “Export worker”).

La registrazione del worker di questo server mostra prima cosa comporta: il gestore di questo server può vedere i dati esportati mentre vengono confezionati, e l’archivio è leggibile sul server (da chiunque abbia il link, dal gestore e da chiunque abbia accesso al suo storage) finché non viene eliminato. Il registro di audit del progetto annota export.operator-visible per ogni esportazione che confeziona. Un worker gestito dall’istituzione tiene fuori chi ospita il server: l’host non detiene mai le sue chiavi. Se scrive anche l’archivio nel proprio storage (la destinazione worker, l’unica offerta dal worker fornito con l’SDK), l’esportazione non esiste mai in chiaro su questo server. Il tuo browser firma la destinazione e le scelte di ogni esportazione, e il worker rifiuta un’esportazione che nessun amministratore ha firmato, così il server non può inviarla altrove.

La registrazione firma un record nel tuo browser, come per i processori: ciò che viene firmato (le chiavi del worker e lo SHA-256 del suo descrittore) viene mostrato prima. Quando un worker viene aggiornato a una versione con altri formati, il suo descrittore cambia e la sua registrazione smette di funzionare: la pagina Esportazione lo segnala, e un amministratore lo registra di nuovo.

Un’esportazione prende un sottoinsieme scelto, non necessariamente tutto:

  • Dati: pazienti, studi e serie, tramite i filtri (centro, modalità, completamento, un campo del modulo clinico) e scegliendo le righe nell’albero. Una serie scelta comprende tutte le sue immagini.
  • Annotazioni: le attività da includere e, per le attività con etichette, quali etichette. Le annotazioni delle etichette escluse non vengono esportate; in una segmentazione, i voxel delle etichette escluse sono impostati a 0.
  • Stati (facoltativo): tutti gli stati per impostazione predefinita, oppure per esempio Solo inviate.
  • Registro di processo (facoltativo, disattivato per impostazione predefinita): come hanno annotato i lettori (elementi, tempo attivo, navigazione, strumenti, tratti, modifiche, strumenti di IA), dal registro di processo del progetto.

La pagina conta ciò che l’esportazione comprende mentre scegli, e ne stima la dimensione. L’archivio conta nella quota di storage del progetto finché non viene eliminato.

Uno snapshot congela una release del dataset (“v1.0”) in modo che possa essere esportata di nuovo, identica, in seguito. Crealo nella pagina Snapshot del progetto: dagli un nome e delle note (entrambi cifrati), scegli se applicare il consenso e le correzioni (dove gli amministratori hanno registrato un consenso, questo sostituisce le risposte dei lettori che combina; altrimenti l’ultima correzione sostituisce quelle che corregge). Uno snapshot prende le annotazioni inviate. Lo snapshot elenca ogni paziente, studio, serie e file del progetto e ogni annotazione alla sua revisione corrente, e il tuo browser firma quell’elenco. Le modifiche successive non lo cambiano: una risposta revisionata resta alla revisione che lo snapshot ha congelato.

Per esportarlo, scegli Esporta sulla sua riga (oppure lo snapshot sotto Cosa esportare nella pagina Esportazione): l’archivio contiene gli oggetti dello snapshot e le sue annotazioni com’erano, e cita lo snapshot in manifest.json e nella scheda del dataset. La pagina verifica la firma dello snapshot rispetto al registro firmato del progetto prima di mostrarlo, confrontarlo o esportarlo; uno snapshot firmato da un amministratore che nel frattempo ha lasciato il progetto o cambiato chiavi non supera più la verifica, e un amministratore ne crea uno nuovo. L’eliminazione dei dati li rimuove comunque, anche dagli snapshot.

La pagina Snapshot permette anche di confrontare due snapshot: cosa il più recente ha aggiunto, rimosso o contiene a un’altra revisione o con un altro stato, per tipo di oggetto e per attività.

  • Formati. Ogni esportazione contiene il layout nativo (sotto). Aggiungi tabelle (CSV, JSON Lines, Parquet), mappe di etichette NIfTI e formati standard (DICOM SEG e SR, nnU-Net, MONAI, BIDS, COCO, Hugging Face) secondo necessità.
  • Destinazione: lo storage di questo server dietro un link, oppure lo storage del worker di esportazione stesso (solo worker gestiti dall’istituzione).
  • Conserva l’archivio per da 1 a 30 giorni (7 per impostazione predefinita), contati da quando è pronto. La data è fissa; non viene mai prorogata.
  • Proteggi il link con un codice (facoltativo): i download richiedono allora il link e un codice generato nel tuo browser. Invia il codice tramite un canale diverso da quello del link.
  • Nomi delle cartelle: ID, oppure i codici pseudonimi dei pazienti e le etichette degli studi e delle serie.
  • Scheda del dataset: un titolo, un riassunto, delle note e una licenza; spunta Includi il protocollo del progetto e le linee guida delle etichette per aggiungerli alla scheda.

Tutto ciò che digiti viene conservato su questo dispositivo mentre lo digiti; non c’è nulla da salvare.

Prima di Conferma, la pagina dice in parole semplici chi decifra questa esportazione e dove l’archivio è conservato e fino a quando. Conferma indica cosa viene esportato. Il tuo browser concede quindi al worker le chiavi esattamente di quegli oggetti e sigilla le opzioni; il server verifica l’elenco ma non decifra mai.

Una volta avviata l’esportazione puoi chiudere la pagina o uscire: prosegue senza il browser. L’elenco delle esportazioni ne mostra l’avanzamento, e ricevi un’email quando è pronta.

  • manifest.json: ogni file di ogni parte, con la sua parte, dimensione, SHA-256 e ruolo.

  • dataset-card.md e dataset-card.json: la scheda del dataset (sotto).

  • annotation_log.jsonl e annotation_log.schema.json, se hai incluso il registro di processo: un evento per riga, con la sua sessione e il lettore (ID utente), per gli elementi esportati e gli eventi senza elemento.

  • project/: le versioni del manifest del progetto con cui sono state fatte le annotazioni.

  • data/: una cartella per paziente, studio e serie con gli originali de-identificati e i metadati di ciascuno (le mesh di superficie mantengono il loro .stl, .ply o .obj; le registrazioni il loro .edf, .bdf o .dcm, e un record WFDB i suoi record.hea e record.dat, così gli strumenti WFDB lo aprono come record nella sua cartella).

  • annotations/: ogni annotazione come file JSON nella forma W3C Web Annotation, per attività, con il nome del membro che l’ha firmata (signedBy, verificato dal worker di esportazione; le risposte la cui firma dell’autore non supera la verifica vengono escluse), e sotto annotations/assets/ i file a cui fanno riferimento, come maschere di segmentazione, file allegati e le registrazioni .wav delle risposte dettate nelle attività che conservano la voce.

  • tables/ (con un formato tabellare): tables/tasks/<task>.csv, una riga per annotazione con il suo target, lettore, stato, revisione e risposta (ogni opzione di una scelta multipla in una colonna propria, le forme in coordinate del volume; su una mesh di superficie le coordinate di un landmark e gli indici dei vertici di una regione), e tables/clinical-form.csv, una riga per paziente con le risposte del modulo clinico.

  • labelmaps/ (con le mappe di etichette NIfTI): ogni segmentazione come file NIfTI sulla geometria della sua serie con solo le etichette selezionate, e un file .json affiancato che indica l’etichetta di ogni valore.

  • geojson/ (progetti con immagini whole-slide, in ogni esportazione): le forme di ciascun lettore per un’attività su un vetrino come file GeoJSON nei pixel a piena risoluzione del vetrino, che QuPath e altri strumenti di anatomia patologica importano; su una pila di piani ogni forma indica il suo plane.

  • Formati standard, ciascuno in una propria cartella con un yggdrasil-report.json che elenca ciò che non ha potuto contenere e perché:

    • dicom-seg/ e dicom-seg-labelmap/: ogni segmentazione di una serie DICOM o di un’immagine DICOM come DICOM Segmentation (un segmento per etichetta, oppure una Label Map Segmentation), con riferimento alle istanze de-identificate, per PACS e visualizzatori DICOM.
    • dicom-sr/: per serie DICOM e lettore, un referto strutturato (TID 1500) con le forme, le loro misure e le risposte di classificazione e numeriche.
    • nnunet/, monai/, bids/: dataset di addestramento di segmentazioni di volumi, ogni immagine convertita in NIfTI sulla griglia su cui giacciono le sue maschere: nnU-Net v2 (un’etichetta per immagine: il consenso, altrimenti quella inviata), un datalist MONAI (l’etichetta di ogni lettore) e BIDS (serie RM il cui contrasto è indicato nell’header, le maschere come derivative).
    • coco/: per attività, le forme e le maschere sulle immagini 2D come COCO JSON, con riferimento agli originali in data/.
    • huggingface/: una scheda del dataset e una configurazione per attività, che datasets.load_dataset legge.

Le tabelle sono scritte in ciascun formato tabellare che hai scelto (.csv, .jsonl, .parquet), con le stesse colonne. La specifica dell’esportazione nativa definisce ogni file.

La scheda del dataset riporta i conteggi per livello, modalità e centro; i profili di de-identificazione con le versioni dei loro strumenti; il protocollo di annotazione (attività, etichette e opzioni con le loro descrizioni, quali attività hanno una fase di correzione, gli stati esportati); la licenza; ciò che la selezione ha escluso; come citare il dataset; e l’attribuzione “Yggdrasil by AImageLab”.

Copia il link dall’elenco delle esportazioni (qualsiasi amministratore può farlo, in qualsiasi momento). Chiunque abbia il link scarica senza accedere; con un link protetto, serve anche il codice. Tratta il link come tratteresti i dati: chi lo possiede può leggere l’archivio fino alla scadenza.

  • Sostituisci il link crea un nuovo link (e un nuovo codice); il vecchio smette subito di funzionare.
  • Revoca disattiva il link ed elimina l’archivio. Non si può annullare.
  • Alla data di scadenza il link smette di funzionare e l’archivio viene eliminato.

Il server registra l’ora di ogni download e la rete approssimativa (per esempio un /24 IPv4), mai l’indirizzo completo; i codici rifiutati sono registrati una volta per rete e per ora, mai il codice digitato.

Un link aperto in un browser mostra la pagina del link: le parti, le loro dimensioni e i loro SHA-256, la scadenza e i comandi da copiare. Un’esportazione grande è suddivisa in parti numerate, ognuna uno zip completo; manifest.json nella parte 1 indica quale parte contiene ciascun file.

Da riga di comando, curl scarica qualsiasi link e riprende un download interrotto (-C -):

Finestra del terminale
# One part
curl -L -C - -o export.zip 'https://<app>/x/<token>'
# Any number of parts, each under its own name
curl -L --remote-name-all $(curl -fsS 'https://<app>/x/<token>?format=txt')
# A protected link: send the code with every request
curl -H 'X-Ygg-Link-Code: <code>' -L -C - -o export.zip 'https://<app>/x/<token>'

Confronta lo SHA-256 di ogni parte con quello elencato nella pagina del link:

Finestra del terminale
sha256sum export-*-part-*.zip # Linux
shasum -a 256 export-*.zip # macOS

Poi verifica i file contenuti rispetto a manifest.json, che elenca lo SHA-256 di ogni file:

Finestra del terminale
python3 - <<'EOF'
import hashlib, json, zipfile, glob
parts = {int(p.rsplit("-", 1)[1][:4]): zipfile.ZipFile(p) for p in glob.glob("export-*-part-*.zip")}
manifest = json.loads(parts[1].read("manifest.json"))
bad = [f["path"] for f in manifest["files"]
if hashlib.sha256(parts[f["part"]].read(f["path"])).hexdigest() != f["sha256"]]
print("all files match" if not bad else f"mismatch: {bad}")
EOF

Un’esportazione in una sola parte salvata come export.zip è la parte 1: aprila come parts = {1: zipfile.ZipFile("export.zip")}.