Mehrere Datensätze aus einer Binary auslesen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Detektor
User
Beiträge: 3
Registriert: Donnerstag 14. August 2014, 17:43

Hallo,

ich habe heute begonnen mit Python zu arbeiten und bin dabei eine zuvor in IDL geschriebene Routine in Python zu schreiben. Der Grund für den Wechsel von IDL zu Python liegt darin, dass IDL die HDF5 Family Split Option nicht beherrscht.
Bevor ich mit Python zu HDF5 Dateien wechsel, möchte ich erstmal alte Binaries einlesen, um die Routine mit meinen alten IDL Routinen vergleichen zu können.
Die Binaries haben einen Header, der die Anzahl (data_length) der Datenpunkte innerhalb eines Datenblocks angibt. Insgesamt gibt es N (num_buff) solcher Blöcke. Im Code habe ich die Anzahl der Blöcke über die Gesamtgröße des Binary files bestimmt. Unter Berücksichtigung, dass Jede Zahl 2byte hat.

Hier der Code soweit:

Code: Alles auswählen

import struct, os

with open('lines/500/digital352_0.bin', 'rb') as f:
    f.seek(0)
    
    len=os.path.getsize('lines/500/digital352_0.bin')
    bytes=f.read(8)
    header_byte=struct.unpack('II',bytes)
    data_length=int(header_byte[0])
    file_size=int(len)
    num_buff=file_size/2/(data_length+2)

    
    print('Total file size in bytes:' +str(file_size) + 'bytes')
    print('Number of data points per frame:' + str(data_length))      
    print('Number of frames per file:' + str(num_buff))

    for i in range(num_buff-1):
        fifo_start=i*(data_length+4)+4
        k=fifo_start+data_length
        for line in f:
            print line[fifo_start]
   
  

f.close()
Mein Problem beginnt in Zeile 18 (Ab hier ist alles Mist). Wie kann ich die N Blöcke sauber auslesen? In IDL funktioniert das in einer Zeile. Ich habe hier das Python-Buch vor mir und surf schon den halben Tag durch das Netz. Ich bin sicher, es gibt eine saubere Lösung, die keine etlichen For-Schleifen benötigt.

Ich hoffe, ich konnte mein Problem verdeutlichen.


Vielen lieben Dank!
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Sollten deine Datei eine IDL .sav Datei sein, dann gibt es eine Funktion in SciPy dafür: http://docs.scipy.org/doc/scipy-0.14.0/ ... adsav.html

Aus deinem Programm wird mir nicht so recht klar, was du genau erreichen möchtest, aber vielleicht hilt ja der Hinweis, dass range() bis zu Argumente entgegennimmt: https://docs.python.org/3.4/library/fun ... func-range

Und natürlich der Hinweis auf die Slicing-Syntax:

Code: Alles auswählen

>>> 'hallo'[1]
'a'
>>> 'hallo'[1:3]
'al'
>>> 'hallo'[:-1]
'hall'
>>> 'hallo'[-1:]
'o'
>>> 'hallo'[::-1]
'ollah'
>>> 'hallo'[::-2]
'olh'
>>> 'hallo'[1:9:-2]
''
>>> 'hallo'[::2]
'hlo'
>>> 'hallo'[:4:2]
'hl'
>>> 'hallo'[0:4:2]
'hl'
>>> 'hallo'[0::2]
'hlo'
>>> 'hallo'[0::2]
Außerdem solltest du keine builtins überschreiben:

Code: Alles auswählen

>>> len('hallo')
5
>>> len = 'test'
>>> len('hallo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
Dasselbe gilt für bytes.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@Detektor: Eine präzisere Beschreibung des Datenformats anstelle von Code der nicht funktioniert und aus dem man deshalb das Format nur raten kann, wäre praktisch gewesen. Da kann nämlich etwas nicht stimmen wenn Du am Anfang 8 Bytes Header liest, bei der Berechnung des Blockstarts dann aber nur einen Versatz von 4 vom Start der Datei dazu rechnest.

Bei den minimalen Headerdaten vor den jeweiligen Datenblöcken (4 Byte, es sei denn auch das stimmt nicht), würde ich aber gar nicht so kompliziert denken, sondern einfach linear die Blöcke lesen und dazwischen halt den jeweiligen Blockheader überlesen. Da die Blockheader kleiner als die Blockgrösse auf Speichermedien ist, müssen die sowieso gelesen werden, ob man die nun auswertet oder nicht.

Sonstige Anmerkungen: `len` ist der Name einer eingebauten Funktion. Den sollte man nicht an etwas anderes binden. Ausserdem ist der Zwischenschritt sinnfrei weil `os.path.getsize()` bereits eine ganze Zahl als Ergebnis liefert. Genau wie die Elemente im `unpack()`-Ergebnis. `bytes` ist der Name eines Datentyps.

``f.close()`` macht keinen Sinn wenn man die Datei mit ``with`` schon schliessen lassen hat.

``f.seek(0)`` ist überflüssig, denn wenn man eine Datei zum Lesen öffnet steht der Dateizeiger sinnvollerweise bereits am Anfang.

Ich komme dann bei so etwas heraus:

Code: Alles auswählen

import struct


def main():
    with open('lines/500/digital352_0.bin', 'rb') as data_file:
        block_length = struct.unpack('I4x', data_file.read(8))[0] * 2
        while True:
            data_file.read(4)  # Skip block header.
            block = data_file.read(block_length)
            if len(block) < block_length:
                break
            # 
            # Do something with block...
            # 


if __name__ == '__main__':
    main()
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Detektor hat geschrieben:In IDL funktioniert das in einer Zeile.
In Python geht das auch in einer Zeile: http://docs.scipy.org/doc/numpy/referen ... emmap.html
Die verschiedenen Arten Binärdateien einzulesen haben wir gerade erst hier diskutiert: http://www.python-forum.de/viewtopic.php?f=1&t=34491
Wenn Du hdf5 Daten lesen und schreiben willst, dann wird Du dafür wohl pytables benutzen, d.h. Du solltest Numpy einigermaßen kennen.
a fool with a tool is still a fool, www.magben.de, YouTube
Detektor
User
Beiträge: 3
Registriert: Donnerstag 14. August 2014, 17:43

Vielen Dank für die antworten. Ich hab auf der Grundlage mal etwas rumprobiert.
Sorry, für mein Kaudawelsch.

Ich hab es jetzt mit numpy hinbekommen. Das eigentliche Problem, das ich aber nicht geschnallt hatte war, dass mein binary big endian war und deswegen natürlich nur Müll rauskam.


Hier einmal der Code:

Code: Alles auswählen

import os, numpy as np, subprocess as sub, sys

##This script is for reading binary files of 
## define single frame data layout
## note: multiframe files just subsequent chunks od this layout
frame_dtype = np.dtype([("hdtext",np.byte,12),("hdnum",np.int32,1),("stream",np.uint16,512*128)])

mycnt=0
file='img100004.bin'

with open(file, "rb") as f:
	while True:
		frame = np.fromfile(f, dtype=frame_dtype, count=1)
		if not frame: break
		mycnt+=1
	
		
	
		# need the array to have the opposite byte order in memory
		swapped_end_frame = frame.byteswap()
		## need the array to have the opposite byte order in memory,
		## and want the dtype to match.
		# swapped_end_frame = frame.byteswap().newbyteorder()
		data=swapped_end_frame["stream"] #data is an 1D array with 65536 entries.


		


		
	
f.close()
Nun steh ich aber vor einem anderen Problem. In IDL lässt sich eine (n x n)-Martix erstellen (z.b. 100x100) und kontinuierlich, Zeile für Zeile mit einem Array aus 10000 Datenpunkten füllen. In Python lese ich überall, dass man im Prinzip jede Zeile einzeln füllen muss. Ich möchte aber nicht 100 einzelne Arrays mit je 1000 Werten vordefinieren müssen - zumal ich für mein echtes Programm im bereich von 1000 x1000 Matrizen bin.

Code: Alles auswählen

import numpy as np

M=np.zeros([16,64]) ##Ich definiere einen 2D Array mit festem Format
		M[:,:]=data[0:1023] 
##data ist ein 1D array mit insgesamt 65000 Werten. Die ersten 1024(16*64) Werte sollen in die Matrix M geschrieben werden 
		print M

Der Fehler:

Code: Alles auswählen

M[:,:]=data[0:1023]
ValueError: could not broadcast input array from shape (1,65536) into shape (16,64)
Im Netz gibt es viele schöne Beispiele, wie man 2D Arrays aufzieht und sie Spaltenweise mit der gleichen Zahl oder Spaltenweise mit 1D Arrays füllt, die funktionieren auch bei mir. Aber wie schaffe ich es, dass der gesamte 1D stream in die 2d Matrix eingelesen wird (Quasi mit "automatischem Zeilenumbruch")


Danke nochmal!
BlackJack

@Detektor: Warum musst Du `M` vorher überhaupt als ”leeres” Array erstellen? Du kannst doch einfach die ersten 1024 Werte (da hast Du übrigens die Grenzen falsch gewählt und auch nicht bedacht dass `data` *zweidimensional* ist!) von `data` ”slicen” und dann die Form entsprechend ändern: ``M = data.flatten()[:1024].reshape((16, 64))``. Eventuell kannst/musst Du da noch einen Aufruf der `copy()`-Methode einfügen wenn die Daten tatsächlich aus dem ursprünglichen Array kopiert werden müssen.

Edit: Nochmal zum Einlesen: Die „endianess” kann man schon beim Datentyp angeben.

Bei `mycnt` ist das `my` überflüssig und `count` könnte man auch ausschreiben. Wenn man es dann noch `frame_count` nennt, weiss man beim lesen des Names auch gleich *was* da gezählt wird. Da das aber überhaupt nicht verwendet wird, kann man es auch einfach komplett weg lassen.

`file` ist der Name eines eingebauten Datentyps. Ausserdem bindest Du da gar kein Dateiobjekt dran sondern einen Datei*namen*.

Wenn man ``with`` benutzt, braucht man `close()` nicht mehr selber aufrufen.

Ich komme dann ungefähr bei so etwas heraus:

Code: Alles auswählen

import numpy as np

FRAME_DTYPE = np.dtype(
    [
        ('hdtext', np.byte, 12),
        ('hdnum', np.int32, 1),
        ('stream', np.uint16, 512 * 128)
    ]
).newbyteorder('>')
 

def read_frames(filename):
    with open(filename, 'rb') as data_file:
        while True:
            frame = np.fromfile(data_file, dtype=FRAME_DTYPE, count=1)
            if not frame:
                break
            yield frame
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Detektor hat geschrieben:Der Fehler:

Code: Alles auswählen

M[:,:]=data[0:1023]
ValueError: could not broadcast input array from shape (1,65536) into shape (16,64)
Probier das mal

Code: Alles auswählen

M=data[0:1023]
M.shape=(16,64)
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@MagBen: Der Slice bei `data` macht so keinen Sinn, denn in der ersten Dimension hat dieses Array nur ein Element. Ob man da nun `data[0:1024]`, `data[0:42]` oder sonstwas schreibt, solange der Endwert >=1 ist, kommt da immer das selbe Ergebnis raus, nämlich die erste Zeile des 2D-Arrays mit allen 65536 Werten.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Dann vielleicht so?

Code: Alles auswählen

M=data[0][0:1023]
M.shape=(16,64)
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@MagBen: Fast, es müssen 1024 Werte sein und nicht nur 1023.
Antworten