使用 header 从 python 中的 C 中读取二进制数据

Read binary data with header from C in python

我曾使用 C 编写二进制格式的文件。我使用的格式如下:

一个header有5个双打(总共40个字节):

fwrite(&FirstNum, sizeof(double), 1, outFile);
fwrite(&SecNum, sizeof(double), 1, outFile);
fwrite(&ThirdNum, sizeof(double), 1, outFile);
fwrite(&FourthNum, sizeof(double), 1, outFile);           
fwrite(&FifthNum, sizeof(double), 1, outFile);

然后我执行了一个 for cicle over 256^3 "particles"。对于每个粒子,我写了 9 个值:第一个是整数,另外 8 个是双精度值,如下所示:

Ntot = 256*256*256
for(i=0; i<Ntot; i++ )
  {
    fwrite(&gp[i].GID, sizeof(int), 1, outFile);

    /*----- Positions -----*/
    pos_aux[X] = gp[i].pos[X];
    pos_aux[Y] = gp[i].pos[Y];
    pos_aux[Z] = gp[i].pos[Z];

    fwrite(&pos_aux[0], sizeof(double), 3, outFile);  //Positions in 3D
    fwrite(&gp[i].DenConCell, sizeof(double), 1, outFile); //Density
    fwrite(&gp[i].poten_r[0], sizeof(double), 1, outFile); //Field 1
    fwrite(&gp[i].potDot_r[0], sizeof(double), 1, outFile); //Field 2
    fwrite(&gp[i].potDot_app1[0], sizeof(double), 1, outFile); //Field 3
    fwrite(&gp[i].potDot_app2[0], sizeof(double), 1, outFile); //Field 4
  }

其中gp只是一个包含我的粒子信息的数据结构。然后,对于 256^3 个粒子中的每一个,我总共使用了 68 个字节:4 个字节用于 int + 8*(8 个字节)用于双精度数。

我需要的是阅读这种格式,但在 python 中以便制作一些图,但我对 python 有点陌生。我已经阅读了一些使用 python 读取二进制格式文件的答案,但我只能阅读我的 header,而不是 "body" 或有关的其余信息粒子。我尝试过的是以下内容:

Npart = 256
with open("./path/to/my/binary/file.bin", 'rb') as bdata:
    header_size = 40 # in bytes           
    bheader = bdata.read(40)
    header_data = struct.unpack('ddddd', bheader)
    FirstNum = header_data[0]
    SecNum = header_data[1]
    ThirdNum = header_data[2]
    FourthNum = header_data[3]
    FifthNum = header_data[4]
    #Until here, if I print each number, I obtain the correct values.
    #From here, is what I've tried in order to read the 9 data of the 
    #particles
    bytes_per_part = 68
    body_size = int( (Npart**3) * bytes_per_part )
    body_data_read = bdata.read(body_size)
    #body_data = struct.unpack_from('idddddddd', bdata, offset=40)
    #body_data = struct.unpack('=i 8d', body_data_read) 
    body_data = struct.unpack('<i 8d', body_data_read)

    #+++++ Unpacking data ++++++ 
    ID_us = body_data[0]
    pos_x_us = body_data[1]
    pos_y_us = body_data[2]
    pos_z_us = body_data[3]
    DenCon_us = body_data[4]

但是当我 运行 我的代码时,我得到这个错误:

body_data = struct.unpack('<i 8d', body_data_read)
struct.error: unpack requires a string argument of length 68

我试过第一行评论:

#body_data = struct.unpack_from('idddddddd', bdata, offset=40)

但错误显示:

struct.error: unpack requires a string argument of length 72

如果我使用

    body_data = struct.unpack('=i 8d', body_data_read) 

或行

    body_data = struct.unpack('<i 8d', body_data_read)

我得到了我首先显示的错误:

struct.error: unpack requires a string argument of length 68

确实,我觉得我完全看不懂字符串字符“=”和“<”,因为有了它们我得到了我需要阅读的假定长度,但我无法阅读。我最终需要的是一个名为 pos_x_us 的数组,其中包含 x 中的所有位置,pos_y_us 中的 y 中的位置,pos_z_us 中的 z 中的位置等等。如果你能给我一些关于如何获得我所需要的东西的想法或启示,我将不胜感激。

出现您的问题是因为缓冲区大小与格式不匹配。让我们尝试一些随机数据。总共 12 个字节,用于 int 和 float。

>>> data = '\xf4\x9f\x97\xcd\xf2\xbe\xd6\x87\x18\xe3\x17\xdf'

如果您不使用“<”、“>”、“=”和“!”,则会出现 padding

Padding is only automatically added between successive structure members. No padding is added at the beginning or the end of the encoded struct.

>>> struct.unpack('id', data)

Traceback (most recent call last):
  File "<pyshell#56>", line 1, in <module>
    struct.unpack('id', data)
error: unpack requires a string argument of length 16

但是

>>> struct.unpack('=id', data)
(-845701132, -1.2217466572589222e+150)

更具体地说,'d' 本身占用 8 个字节,'i' 占用 4 个字节。'iii' 单独占用 12 个字节就可以了,因为它们是同一类型。但是如果你尝试做 'id',它不会喜欢那样,它会将整数填充到 8 个字节。您可以看到 'c' 占用 1 个字节,但 'ci' 需要 8 个字节。基本上,struct.unpack('ddddd') 因情况而工作正常。

您的其他错误来自格式与缓冲区大小不匹配。如果使用 struct.unpack(), it must match exactly, but if you use struct.unpack_from(),则必须至少具有格式的大小。让我们尝试使用 24 个字节的数据。

# this will fetch 12 bytes, even if the stream has more
>>> struct.unpack_from('=id', 2*data)
(-845701132, -1.2217466572589222e+150)

但是

>>> struct.unpack('=id', 2*data)

Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    struct.unpack('=id', 2*data)
error: unpack requires a string argument of length 12

如您现在所见,您的数据实际上是

body_size = int( (Npart**3) * bytes_per_part )
body_data_read = bdata.read(body_size)

为了匹配它,您需要 'i8di8di8d...' Npart**3 次的格式。所以,

body_data = struct.unpack('='+(Npart**3)*'i8d', body_data_read)

现在您已经一次读入了所有数据,您可以开始根据需要拆分它们。例如,第二个值具有第一个粒子的 x 坐标,由于此模式每 9 个值重复一次,您可以通过切片获得所有粒子的 x 坐标。

pos_x_us = body_data[1::9]