在Python中读取二进制数据

7
首先,在此问题被标记为重复之前,我知道其他人提出了类似的问题,但似乎没有清晰的解释。我正在尝试将二进制文件读入到一个2D数组中(在这里有很好的文档记录:http://nsidc.org/data/docs/daac/nsidc0051_gsfc_seaice.gd.html)。
头文件是一个300字节的数组。
到目前为止,我已经做到了;
import struct

with open("nt_197912_n07_v1.1_n.bin",mode='rb') as file:
    filecontent = file.read()

x = struct.unpack("iiii",filecontent[:300])

抛出一个字符串参数长度错误。

1个回答

6

读取数据(简答题)

在确定了网格的大小(n_rowsxn_cols = 448x304)后,您可以使用numpy.frombuffer简单地读取数据。

import numpy as np

#...

#Get data from Numpy buffer
dt = np.dtype(('>u1', (n_rows, n_cols)))
x = np.frombuffer(filecontent[300:], dt) #we know the data starts from idx 300 onwards

#Remove unnecessary dimension that numpy gave us
x = x[0,:,:]

'>u1'指定了数据的格式,本例中为1字节大小的无符号整数,其大端格式。

使用matplotlib.pyplot绘制此图。

import matplotlib.pyplot as plt

#...

plt.imshow(x, extent=[0,3,-3,3], aspect="auto")
plt.show()

"extent=选项仅指定轴值,您可以将其更改为例如经度/纬度(从标题解析)"

Output

.unpack()的错误解释

struct.unpack(fmt, string)的文档中可以得知:

字符串必须恰好包含格式所需的数据量(len(string)必须等于calcsize(fmt))。

您可以通过查看格式字符部分来确定格式字符串(fmt)中指定的大小。

struct.unpack("iiii",filecontent[:300])中,您的fmt指定了4个int类型(您也可以使用4i=iiii来简化),每个类型的大小为4,需要一个长度为16的字符串。

而您的字符串(filecontent[:300])长度为300,而您的fmt要求一个长度为16的字符串,因此出现了错误。

.unpack()的使用示例

作为例子,阅读您提供的文档,我提取了前21*6字节,其格式如下:

一个由21个6字节字符字符串组成的数组,其中包含有极地立体投影网格特征等信息。

使用:
x = struct.unpack("6s"*21, filecontent[:126])

这将返回一个由21个元素组成的元组。注意某些元素中的空格填充以满足6字节的要求。
>> print x
    # ('00255\x00', '  304\x00', '  448\x00', '1.799\x00', '39.43\x00', '45.00\x00', '558.4\x00', '154.0\x00', '234.0\x00', '
    # SMMR\x00', '07 cn\x00', '  336\x00', ' 0000\x00', ' 0034\x00', '  364\x00', ' 0000\x00', ' 0046\x00', ' 1979\x00', '  33
    # 6\x00', '  000\x00', '00250\x00')

注:

  • 第一个参数fmt"6s"*21是一个字符串,其中6s重复了21次。每个格式字符6s表示一个6字节的字符串(见下文),这将匹配您文档中指定的所需格式。
  • filecontent[: 126]中的数字126计算为6*21 = 126
  • 请注意,在s(字符串)说明符中,前面的数字不表示要重复格式字符6次(因为对于其他格式字符通常会这样做)。相反,它指定了字符串的大小。s表示1字节字符串,而6s表示6字节字符串。

更详细的标头读取解决方案(长)

由于必须手动指定二进制数据,因此在源代码中可能很繁琐。您可以考虑使用一些配置文件(如.ini文件)。

这个函数将读取头文件并将其存储在一个字典中,其中结构是从一个.ini文件中给定的。
# user configparser for Python 3x
import ConfigParser

def read_header(data, config_file):
    """
    Read binary data specified by a INI file which specifies the structure
    """

    with open(config_file) as fd:

        #Init the config class
        conf = ConfigParser.ConfigParser()
        conf.readfp(fd)

        #preallocate dictionary to store data
        header = {}

        #Iterate over the key-value pairs under the
        #'Structure' section
        for key in conf.options('structure'):

            #determine the string properties
            start_idx, end_idx = [int(x) for x in conf.get('structure', key).split(',')]
            start_idx -= 1 #remember python is zero indexed!
            strLength = end_idx - start_idx

            #Get the data
            header[key] = struct.unpack("%is" % strLength, data[start_idx:end_idx])

            #Format the data
            header[key] = [x.strip() for x in header[key]]
            header[key] = [x.replace('\x00', '') for x in header[key]]

        #Unmap from list-type
        #use .items() for Python 3x
        header = {k:v[0] for k, v in header.iteritems()}

    return header

下面是一个示例的.ini文件。键是存储数据时使用的名称,值是由逗号分隔的一对值,第一个值是起始索引,第二个值是结束索引。这些值来自于你文档中的表1。
[structure]
missing_data: 1, 6
n_cols: 7, 12
n_rows: 13, 18
latitude_enclosed: 25, 30

这个函数可以按以下方式使用:

header = read_header(filecontent, 'headerStructure.ini')
n_cols = int(header['n_cols'])

非常感谢您的回复和时间,我会尝试这个方法并稍后给您答复。如果它起作用了,我会标记答案。对我来说,将网格化数据存储在NetCDF中似乎更合乎逻辑,但是呢! - J W
1
所以针对这个问题进行转化,最后一行应该写成 x = struct.unpack("75i", filecontent[:300]) - Aemyl
所以答案告诉了我如何获取一些标题,问题是如何修改struct.unpack以获取304列x448行的数据(尽管它可能会给我一串数字而不是2D矩阵)。 - J W
@JamesWarner,已更新答案以读取您的数据。我还包括了一个函数示例,以帮助您使用结构读取头文件。 - Jamie Phan
1
@JamesWarner,已更新答案。问题出在使用有符号的“int”('i')而不是无符号的“int”('u'),因为值的范围从0到255。我无法正确地用灰色掩盖空值,如果您查看浏览比较图像,您可以看到差异。 - Jamie Phan
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接