• Non ci sono risultati.

Pianificazione del lavoro

Nel documento Web based graph library (pagine 32-42)

Il lavoro, della durata di 8 settimane, è stato pianificato utilizzando la metodologia SCRUM con degli sprint bisettimanali.

3https://www.apache.org/licenses/LICENSE-2.0 4

In principo ci si è concentrati sulla gestione dei dati e sulla visualizzazione di un grafico per ogni canale. Durante questa fase si sono incontrati parecchi problemi nella gestione delle performance, soprattutto legati alla grande quantità di dati da caricare, analizzare e servire all’interfaccia grafica. Si è perciò dovuto dedicare parecchio tempo allo studio e all’implementazione di varie soluzioni che permettessero un caricamento, una lettura ed un’elaborazione dei dati maggiormente efficente. Si è cercato di bilanciare il carico di lavoro tra server e browser in modo da non sovraccaricare nessuna delle due componenti e di mantenere un’esperienza d’uso fluida e reattiva.

Una volta risolti i problemi di performance ci si è dedicati all’implementazione di un server che integrasse un database ed alla creazione di tutti gli elementi necessari, come le entità da persistere e gli endpoint da esporre, alla comunicazione tra interfaccia e backend. La tappa successiva è stata migliorare e aggiungere funzionalità all’interfaccia grafica in modo da ottenere un’esperienza d’uso che non si distaccasse troppo da Remlogic.

L’ultimo passo è stato separare definitivamente il codice che fa parte della libreria per l’an- notazione dei grafici dal resto del sistema e l’implementazione di un modo per aggiungere varie annotazioni ai diagrammi.

Capitolo 5

Risultati

5.1

Server

Il server è stato implementato utilizzando il framework Spring Boot e sfrutta MySQL per persistere le informazioni riguardanti i pazienti e le registrazioni. Inoltre fornisce svariati endpoint sfruttabili dall’interfaccia per richiedere e modificare informazioni sulle entità del DB e per ottenere i dati da visualizzare nei grafici.

5.1.1

Database

Grazie al framework Java Persistence API (JPA) si può facilmente associare il DBMS My- SQL al server.

Il database è composto da tre entità: Patient, Recording e Annotation.

Patient contiene tutte le informazioni riguardanti il paziente (Nome, Cognome, . . . ) ed un riferimento ad una lista delle sue registrazioni.

Una registrazione è rappresentata dall’entità Recording, la quale è composta da un id, due valori numerici che indicano il timestamp dell’inizio e della fine della registrazione (start, end), una lista di stringhe che rappresentano i sensori (channels) legati alla registrazione, una lista delle frequenze di campionamento (samplingsRate) per ogni sensore, una stringa che indica il nome del file contenente i dati (traces) e un’altra stringa che indica il nome del file contenente le annotazioni (annotations).

Un’Annotation rappresenta una nota aggiunta ad un grafico. Essa ha, oltre all’id, un campo che indica di quale evento si tratta, il timestamp dell’annotazione e la sua durata. Inoltre si salva anche un riferimento a quale registrazione è associata l’annotazione.

È stata creata una classe Traces che contiene: un array bidimensionale di float, che rap- presenta i valori delle registrazioni (data), una mappa, che associa ad ogni frequenza di campionamento un array di timestamps e due array di float, che contengono per ogni sen- sore il valore massimo e il valore minimo registrato (maxValues e minValues).

La registrazione di una sessione di sonno è formata normalmente da 8 ore di registrazione e quasi 50 sensori. Questo significa che per un canale che viene campionato a 200 Hz si hanno quasi 6 milioni di valori. Se ogni valore registrato viene salvato in un float si ottiene che un solo canale con campionamento a 200Hz occupa circa 23 MB di spazio. Per que- stioni di performance è quindi meglio salvare tutti i dati di una registrazione direttamente sul file system e mantenere nel DB solamente il riferimento al nome del file salvato. Per ogni registrazione si crea quindi un oggetto della classe serializzabile Traces che viene salvato come file binario direttamente sul file system e nel DB viene mantenuto solamente il riferi- mento al suo nome.

5.1.2

Struttura Classi

Il server è strutturato secondo la consueta configurazione di un progetto Spring. Nel packa- ge model sono presenti le entità Annotation, Patient e Recording che vengono persistite nel database, la classe Traces utilizzata per salvare nel file system i dati delle registrazioni e la classe Annotation utilizzata per costruire le annotazioni per i grafici.

Il collegamento tra il server ed il database avviene, grazie a Spring Data JPA, nelle classi PatientRepository e RecordingRepository del package repository. Queste classi vengono utilizzate dai rispettivi service per eseguire operazioni CRUD nel DB. Inoltre è presente un package storage le cui classi si occupano di gestire il salvataggio, il caricamento e la cancellazione dei file del file system del server.

5.1.2.1 EDFParser

Per leggere e convertire i file EDF si è sfruttato un parser scritto in Java distribuito con li- cenza MIT. Questo parser consente di leggere un file EDF, estrarre le informazioni salvate nell’header e i dati delle registrazioni. La grande quantità di dati da leggere ed elaborare causava però problemi legati all’occupazione della memoria. Si è quindi deciso di modifi- care leggermente le classi, rimuovendo i parametri ritenuti non necessari e modificando gli oggetti in cui si salvavano i dati. In particolare essi venivano salvati utilizzando la primitiva Java "double". In Java un double occupa uno spazio pari a 64-bit, si è deciso quindi di salvare tutti i dati utilizzando la primitiva "float" che occupa solamente 32-bit. Nonostante la diminuzione della precisione dei dati non sono state riscontrate delle differenze visive nella rappresentazione dei segnali, mentre i problemi di performance e di occupazione della me- moria sono drasticamente diminuiti. Questo parser è contenuto nel package EDFParser ed è formato da varie classi. Le informazioni sul paziente e la registrazione sono salvate nella classe EDFHeader, mentre i dati sono contenuti nella classe EDFSignal.

5.1.3

Downsampler

Il sottocampionamento dei dati avviene all’interno di una classe che estende l’interfaccia Do- wnsampler, che esponde un metodo getDownsampledPoint il quale, dati una lista di coor- dinate (coppie valore-timestamp) e una risoluzione desiderata ritorna la lista di coordinate filtrata. Sono state implementati due tipi di Downsampler e si è confrontato il risultato di ognuno dei due con un grafico ottenuto utilizzando tutti i punti disponibili. La classe Median- Downsampler divide i dati ricevuti in N blocchi uguali, dove N è il valore più vicino possibile alla risoluzione desiderata che consente di avere blocchi uguali. Ad esempio se si ha una lista di 100 valori e si vuole una risoluzione di 23 dati non sarà possibile dividere i 100 valori in 23 blocchi di uguale dimensione. È quindi necessario trovare il primo valore maggiore di

23 che sia un divisore di 100, ovvero 25. Una volta divisi i dati in blocchi uguali si effettua la media di ogni blocco e si genera una lista finale di 25 elementi contenti le varie medie. Un altro tipo di sottocampionamento avviene nella classe NaiveDownsampler. Questa clas- se offre un sottocampionamento molto semplice, ma con performance elevate. Data una lista di dati N ed una risoluzione M si calcola un valore X, detto step, pari al quoziente N/M. In seguito si scorre la lista di coordinate e si estrae un valore ogni X.

5.1.3.1 Confronto Downsampler

Per verificare quale Downsampler utilizzare si è confrontato il grafico generato da ognuno dei due metodi (naive o media) con un grafico generato tramite echarts al quale venivano passati tutti i dati.

Figura 5.1: Confronto tra Downsampler

Nella figura 5.1 è possibile osservare in alto un grafico generato senza sottocampiona- mento lato server, nel mezzo un grafico con il sottocampionamento naive e in basso con un sottocampionamento effettuato usando la media. Nonostante sia evidente una perdita di precisione il grafico più somigliante all’originale è dato dal sottocampionamento naive, mentre utilizzando la media vi è uno "smussamento" dei dati che modifica eccessivamente l’andamento della linea.

Un risultato ancora migliore sarebbe ottenibile utilizzando altri metodi di sottocampionamen- to più sofisticati che garantiscano risultati visivamente ideali, come il metodo del Largest- Triangle.[4]

5.1.3.2 Controller

Il package controller è composto da cinque classi controller:

AnnotationController si occupa di mappare le richiesta GET e POST all’endpoint

"/annotations"

PagesController è una classe che si occupa di mappare la pagine index.html, ovvero

la pagina che corrisponde all’interfaccia del sistema, al percorso di base “/”. Nella pagina vengono iniettate alcuni oggetti che vengono gestiti tramite Thymeleaf.

PatientController si occupa di gestire le operazioni riguardanti i pazienti quali la

creazione, la modifica o l’eliminazione di un paziente. Queste operazioni avvengono tramite richieste GET, POST, PUT o DELETE all’endpoint "/patient".

ChartsController invece si occupa di gestire vari endpoint relativi ai grafici

RecordingsController è il controller che gestisce il caricamento del file edf e la

conversione dei dati

L’elaborazione dei dati delle registrazioni avviene all’interno del controller ChartsController, il quale offre i seguenti endpoint:

GET /start?filename=XXX

– REQUEST PARAM filename: nome del file da cui recuperare la registrazione – RETURN: ritorna il timestamp in millisecondi dell’inizio delle registrazioni

GET /end?filename=XXX

– REQUEST PARAM filename: nome del file da cui recuperare la registrazione – RETURN: ritorna il timestamp in millisecondi della fine delle registrazioni

GET /trace?filename=XXX&channel=000&start=000&end=00=&res=000 – REQUEST PARAM filename: nome del file da cui recuperare la registrazione – REQUEST PARAM channel: id del canale di cui si vogliono i dati

– REQUEST PARAM start: timestmap dell’inizio dei dati desiderati (opzionale) – REQUEST PARAM end : timestamp della fine dei dati (opzionale)

– REQUEST PARAM res: numero di punti da ritornare (opzionale)

– RETURN: ritorna il timestamp in millisecondi dell’inizio delle registrazioni (opzio-

nale)

– OSSERVAZIONI: Questo endpoint ritorna una lista di coppie valore-timestamp.

In questo endpoint è obbligatorio specificare il nome del file e il canale richiesto, mentre se gli altri parametri non verranno specificati si ritornerà l’intero segnale con una risoluzione default di 2000.

GET /channels?filename=XXX - ritorna la lista dei label dei canali di un determinato

file

– REQUEST PARAM filename: nome del file da cui recuperare la registrazione – RETURN: ritorna la lista dei label dei canali della registrazione

GET /load?filename=XXX

– REQUEST PARAM filename: nome del file da cui recuperare la registrazione – OSSERVAZIONI: questo endpoint serve per indicare al server di iniziare a ca-

ricare il file di registrazione e delle annotazioni in memoria. Grazie a questo endpoint è possibile precaricare i dati in memoria diminuendo il tempo di attesa di ricezione dei file nell’interfaccia. Il caricamento del file avviene all’interno di un metodo sincronizzato in modo da evitare qualsiasi problema di race condition nel caso in cui si abbiano più richieste in contemporanea.

Annotations Controller espone i seguenti endpoint:

GET /annotations?filename=XXX&start=000&scrollSize=000

– REQUEST PARAM filename: nome del file da cui recuperare la registrazione – REQUEST PARAM start: valore da cui iniziare a recuperare le annotazioni

(opzionale)

– REQUEST PARAM scrollSize: dimensioni delle finestre di tempo delle annota-

zioni

– RETURN: le annotazioni del file specificato come parametro

– OSSERVAZIONI: Vi sono due modi per effettuare una richiesta a questo end-

point:

∗ Specificando start e scrollSize il server ritornerà una lista di 3 array con- tentene le annotazioni "correnti", ovvero dal timestamp specificato in start per una durata pari al valore del parametro scrollSize, le annotazioni della finestra precedente e quelle della finestra successiva

∗ Specificando solamente il nome del file verrà ritornata un’unica lista conten- te tutti gli eventi del file delle annotazioni

– REQUEST PARAM duration: durata dell’evento, può valere 0 in caso di eventi

istantanei

– REQUEST PARAM event: stringa che indica di quale evento si tratta

– REQUEST PARAM timestamp: timestamp in millisecondi di inizio dell’evento – REQUEST PARAM recording: nome del file associato alla registrazione – RETURN: una risposta con bosy vuoto e stato OK

– OSSERVAZIONI: Questo metodo serve per salvare nel database un’annotazio-

ne inserita lato frontend

Il caricamento e il trattamento dei dati di una polisonnografia avviene all’interno della classe RecordingsController, che espone il seguente endpoint:

POST /recording?file=XXX&annotations=XXX&id=000. – REQUEST PARAM file: file EDF della polisonnografia

– REQUEST PARAM annotations: file .txt delle annotazioni esportato tramite Rem-

Logic

– REQUEST PARAM id : id del paziente a cui associare la registrazione

– OSSERVAZIONI: In questo metodo dapprima si recupera il paziente dal DB tra-

mite l’ID e gli si associa una nuova registrazione. In seguito si eseguono le seguenti operazioni:

∗ Lettura del file EDF con EDFParser

∗ Estrazione dei timestamp di inizio e fine, di una lista dei label dei canali e delle frequenze di campionamento con RecordingParser

∗ Salvataggio del file di annotazioni su disco

∗ Salvataggio delle tracce della registrazione su disco

∗ Persistenza della registrazione sul DB

Per ogni file da salvare su disco viene generato un nome che è composto dalle iniziali del nome, dalla data di registrazione, dall’id del paziente, una serie di 3 numeri identificativi del file e un suffisso che indica se è un file di dati o di annotazioni.

Nel documento Web based graph library (pagine 32-42)

Documenti correlati