2011-09-27 13 views
13

I set di dati molto grandi sono archiviati in file binari sul disco rigido. Ecco un esempio della struttura dei file:Modo efficiente per creare array di Numpy da file binari

Header File

149 Byte ASCII Header 

Registrazione Avvia

4 Byte Int - Record Timestamp 

esempio Avviare

2 Byte Int - Data Stream 1 Sample 
2 Byte Int - Data Stream 2 Sample 
2 Byte Int - Data Stream 3 Sample 
2 Byte Int - Data Stream 4 Sample 

fine campione

Ci sono 122,880 campioni per record e 713 record per file. Questo produce una dimensione totale di 700,910,521 byte. La frequenza di campionamento e il numero di record variano a volte, quindi devo codificare per rilevare il numero di ciascun file.

Attualmente il codice che uso per importare questi dati in array funziona così:

from time import clock 
from numpy import zeros , int16 , int32 , hstack , array , savez 
from struct import unpack 
from os.path import getsize 

start_time = clock() 
file_size = getsize(input_file) 

with open(input_file,'rb') as openfile: 
    input_data = openfile.read() 

header = input_data[:149] 
record_size = int(header[23:31]) 
number_of_records = (file_size - 149)/record_size 
sample_rate = ((record_size - 4)/4)/2 

time_series = zeros(0,dtype=int32) 
t_series = zeros(0,dtype=int16) 
x_series = zeros(0,dtype=int16) 
y_series = zeros(0,dtype=int16) 
z_series = zeros(0,dtype=int16) 

for record in xrange(number_of_records): 

    time_stamp = array(unpack('<l' , input_data[ 149 + (record * record_size) : 149 + (record * record_size) + 4 ]) , dtype = int32) 
    unpacked_record = unpack('<' + str(sample_rate * 4) + 'h' , input_data[ 149 + (record * record_size) + 4 : 149 + ((record + 1) * record_size) ]) 

    record_t = zeros(sample_rate , dtype=int16) 
    record_x = zeros(sample_rate , dtype=int16) 
    record_y = zeros(sample_rate , dtype=int16) 
    record_z = zeros(sample_rate , dtype=int16) 

    for sample in xrange(sample_rate): 

    record_t[sample] = unpacked_record[ (sample * 4) + 0 ] 
    record_x[sample] = unpacked_record[ (sample * 4) + 1 ] 
    record_y[sample] = unpacked_record[ (sample * 4) + 2 ] 
    record_z[sample] = unpacked_record[ (sample * 4) + 3 ] 

    time_series = hstack ((time_series , time_stamp)) 
    t_series = hstack ((t_series , record_t)) 
    x_series = hstack ((x_series , record_x)) 
    y_series = hstack ((y_series , record_y)) 
    z_series = hstack ((z_series , record_z)) 

savez(output_file, t=t_series , x=x_series ,y=y_series, z=z_series, time=time_series) 
end_time = clock() 
print 'Total Time',end_time - start_time,'seconds' 

questo richiede attualmente circa 250 secondi per 700 file di MB, che mi sembra molto alto. C'è un modo più efficiente che potrei fare?

Soluzione Finale

Utilizzando il metodo fromfile NumPy con un costume DTYPE tagliare il tempo di esecuzione a 9 secondi, 27x più veloce rispetto al codice originale di cui sopra. Il codice finale è sotto.

from numpy import savez, dtype , fromfile 
from os.path import getsize 
from time import clock 

start_time = clock() 
file_size = getsize(input_file) 

openfile = open(input_file,'rb') 
header = openfile.read(149) 
record_size = int(header[23:31]) 
number_of_records = (file_size - 149)/record_size 
sample_rate = ((record_size - 4)/4)/2 

record_dtype = dtype([ ('timestamp' , '<i4') , ('samples' , '<i2' , (sample_rate , 4)) ]) 

data = fromfile(openfile , dtype = record_dtype , count = number_of_records) 
time_series = data['timestamp'] 
t_series = data['samples'][:,:,0].ravel() 
x_series = data['samples'][:,:,1].ravel() 
y_series = data['samples'][:,:,2].ravel() 
z_series = data['samples'][:,:,3].ravel() 

savez(output_file, t=t_series , x=x_series ,y=y_series, z=z_series, fid=time_series) 

end_time = clock() 

print 'It took',end_time - start_time,'seconds' 
+0

E 'dati medici? EDF? Se non sai di cosa sto parlando, non importa ...; o) In ogni caso, dai un'occhiata alla mia risposta, che uso per aprire i file binari dei dati medici in base a questa domanda: http://stackoverflow.com/q/5804052/401828. C'è una discussione interessante lì. – heltonbiker

+0

Nessun dato è geofisico. Ho visto la tua domanda durante la ricerca prima di postare. I tuoi dati non sono nient'altro che brevi, in cui sfortunatamente ho i timestamp a 4 byte int sparsi per tutto il flusso. – Stu

+0

Per quello che vale, molte operazioni su array di numpy strutturati sono molto più lente rispetto a normali array di numpy. Il tempo di importazione potrebbe essere più veloce, ma i calcoli potrebbero richiedere 10-100 volte di più :( –

risposta

13

Alcuni indizi:

Qualcosa di simile (non testato, ma si ottiene l'idea):

 
import numpy as np 

file = open(input_file, 'rb') 
header = file.read(149) 

# ... parse the header as you did ... 

record_dtype = np.dtype([ 
    ('timestamp', '<i4'), 
    ('samples', '<i2', (sample_rate, 4)) 
]) 

data = np.fromfile(file, dtype=record_dtype, count=number_of_records) 
# NB: count can be omitted -- it just reads the whole file then 

time_series = data['timestamp'] 
t_series = data['samples'][:,:,0].ravel() 
x_series = data['samples'][:,:,1].ravel() 
y_series = data['samples'][:,:,2].ravel() 
z_series = data['samples'][:,:,3].ravel() 
+0

Il tempo totale ora è 9,07 secondi incluso il salvataggio! Grazie. Aggiornerò la domanda con il codice finale. – Stu

+0

Ottima risposta, ottimo uso della funzionalità incorporata numpy! +1 – heltonbiker

+0

Come fai a sapere se hai un'intestazione e quanto tempo è? – mmann1123

2

Numpy supporta mapping binario di dati direttamente nella matrice come oggetti tramite numpy.memmap. Potrebbe essere possibile memmap del file ed estrarre i dati necessari tramite offset.

Per endianness correttezza basta usare numpy.byteswap su quello che avete letto in È possibile utilizzare un'espressione condizionale per controllare l'endianness del sistema host.

if struct.pack('=f', np.pi) == struct.pack('>f', np.pi): 
    # Host is big-endian, in-place conversion 
    arrayName.byteswap(True) 
+0

L'ho visto, ma sembra che non ci sia modo di specificare l'endianità dei dati. Il codice deve funzionare sotto entrambe le finestre e unix in modo che l'endianità debba essere dichiarata esplicitamente – Stu

+0

Puoi usare [numpy.byteswap] (http://docs.scipy.org/doc/numpy/reference/generated/numpy.memmap.html) per impostare il corretto endianness dei dati sul posto, se necessario.Vedere la modifica – talonmies

+0

+1 per la funzione 'numpy.memmap', che sembra molto utile – heltonbiker

2

Un lampante inefficienza è l'uso di hstack in un ciclo:

time_series = hstack ((time_series , time_stamp)) 
    t_series = hstack ((t_series , record_t)) 
    x_series = hstack ((x_series , record_x)) 
    y_series = hstack ((y_series , record_y)) 
    z_series = hstack ((z_series , record_z)) 

su ogni iterazione, questa alloca una matrice leggermente più grande per ogni serie e copia tutti i dati letti finora in esso. Ciò comporta lotti di copia non necessaria e può potenzialmente causare una frammentazione della memoria non valida.

mi piacerebbe accumulare i valori di time_stamp in un elenco ed effettuare una hstack alla fine, e farebbe esattamente lo stesso per record_t ecc

Se questo non porta miglioramenti delle prestazioni sufficienti, mi piacerebbe commenta il corpo del ciclo e inizierebbe a riportare le cose in una volta sola, per vedere dove è trascorso esattamente il tempo.

+0

Va bene l'implementazione di questo ha ridotto il tempo a 110 secondi !! Grazie, sono provare a implementare anche altri suggerimenti: – Stu

+0

Anche dei 110 secondi ora, circa 40 sono per la funzione savez che non riesco ad ottimizzare, anche se per il confronto il caricamento di .npz richiede solo 20 secondi. – Stu

+0

Devo avere ape n sbagliato su savez come usare il metodo fromfile con un dtype personalizzato il tempo compreso savez sceso a 9 secondi. – Stu

0

Ho ottenuto risultati soddisfacenti con un problema simile (file di dati binari multicanale multi-risoluzione) utilizzando array e struct.unpack.Nel mio problema, volevo dati continui per ogni canale, ma il file aveva una struttura orientata all'intervallo, anziché una struttura orientata ai canali.

Il "segreto" è leggere l'intero file prima, e solo allora distribuire le fette noti imprese ai contenitori desiderati (sul codice sotto, self.channel_content[channel]['recording'] è un oggetto di tipo array):

f = open(somefilename, 'rb')  
fullsamples = array('h') 
fullsamples.fromfile(f, os.path.getsize(wholefilename)/2 - f.tell()) 
position = 0 
for rec in xrange(int(self.header['nrecs'])): 
    for channel in self.channel_labels: 
     samples = int(self.channel_content[channel]['nsamples']) 
     self.channel_content[channel]['recording'].extend(fullsamples[position:position+samples]) 
      position += samples 

Naturalmente, non posso affermare che questo è migliore o più veloce di altre risposte fornite, ma almeno è qualcosa che potresti valutare.

Spero che aiuti!

Problemi correlati