2.2.1.3 数据类型
2.2.1.3.1 描述符
dtype
描述了数组里的一个项目:
type | 数据的标量类型,int8、int16、float64等之一(固定大小),str、unicode、void(可变大小) |
itemsize | 数据块的大小 |
byteorder | 字节序: big-endian > / little-endian < / 不可用 |
fields | 子-dtypes,如果是一个结构化的数据类型 |
shape | 数组的形状,如果是一个子数组 |
In [13]:
np.dtype(int).type
Out[13]:
numpy.int64
In [14]:
np.dtype(int).itemsize
Out[14]:
8
In [15]:
np.dtype(int).byteorder
Out[15]:
'='
2.2.1.3.2 例子:读取.wav文件
The.wav
file header:
chunk_id | "RIFF" |
chunk_size | 4字节无符号little-endian整型 |
format | "WAVE" |
fmt_id | "fmt " |
fmt_size | 4字节无符号little-endian整型 |
audio_fmt | 2字节无符号little-endian整型 |
num_channels | 2字节无符号little-endian整型 |
sample_rate | 4字节无符号little-endian整型 |
byte_rate | 4字节无符号little-endian整型 |
block_align | 2字节无符号little-endian整型 |
bits_per_sample | 2字节无符号little-endian整型 |
data_id | "data" |
data_size | 4字节无符号little-endian整型 |
- 44字节块的原始数据(在文件的开头)
- …接下来是
data_size
实际声音数据的字节。
.wav
文件头是Numpy结构化数据类型:
In [6]:
wav_header_dtype = np.dtype([
("chunk_id", (str, 4)), # flexible-sized scalar type, item size 4
("chunk_size", "<u4"), # little-endian unsigned 32-bit integer
("format", "S4"), # 4-byte string
("fmt_id", "S4"),
("fmt_size", "<u4"),
("audio_fmt", "<u2"), #
("num_channels", "<u2"), # .. more of the same ...
("sample_rate", "<u4"), #
("byte_rate", "<u4"),
("block_align", "<u2"),
("bits_per_sample", "<u2"),
("data_id", ("S1", (2, 2))), # sub-array, just for fun!
("data_size", "u4"),
#
# the sound data itself cannot be represented here:
# it does not have a fixed size
])
也可以看一下wavreader.py
In [5]:
wav_header_dtype['format']
Out[5]:
dtype('S4')
In [6]:
wav_header_dtype.fields
Out[6]:
<dictproxy {'audio_fmt': (dtype('uint16'), 20),
'bits_per_sample': (dtype('uint16'), 34),
'block_align': (dtype('uint16'), 32),
'byte_rate': (dtype('uint32'), 28),
'chunk_id': (dtype('S4'), 0),
'chunk_size': (dtype('uint32'), 4),
'data_id': (dtype(('S1', (2, 2))), 36),
'data_size': (dtype('uint32'), 40),
'fmt_id': (dtype('S4'), 12),
'fmt_size': (dtype('uint32'), 16),
'format': (dtype('S4'), 8),
'num_channels': (dtype('uint16'), 22),
'sample_rate': (dtype('uint32'), 24)}>
In [7]:
wav_header_dtype.fields['format']
Out[7]:
(dtype('S4'), 8)
- 第一个元素是结构化数据中对应于名称
format
的子类型 - 第二个是它的从项目开始的偏移(以字节计算)
练习
小练习,通过使用偏移来创造一个“稀释”的dtype,只使用一些字段:
In [ ]:
wav_header_dtype = np.dtype(dict(
names=['format', 'sample_rate', 'data_id'],
offsets=[offset_1, offset_2, offset_3], # counted from start of structure in bytes
formats=list of dtypes for each of the fields,
))
并且用它来读取sample rate和data_id
(就像子数组)。
In [7]:
f = open('data/test.wav', 'r')
wav_header = np.fromfile(f, dtype=wav_header_dtype, count=1)
f.close()
print(wav_header)
[ ('RIFF', 17402L, 'WAVE', 'fmt ', 16L, 1, 1, 16000L, 32000L, 2, 16, [['d', 'a'], ['t', 'a']], 17366L)]
In [8]:
wav_header['sample_rate']
Out[8]:
array([16000], dtype=uint32)
让我们访问子数组:
In [9]:
wav_header['data_id']
Out[9]:
array([[['d', 'a'],
['t', 'a']]],
dtype='|S1')
In [10]:
wav_header.shape
Out[10]:
(1,)
In [11]:
wav_header['data_id'].shape
Out[11]:
(1, 2, 2)
当访问子数组时,维度被添加到末尾!
注意:有许多模块可以用于加载声音数据,比如wavfile
、audiolab
等…
2.2.1.3.3 投射和再解释/视图
投射
- 赋值
- 数组构建
- 算术
- 等等
- 手动:
.astype(dtype)
data re-interpretation
- 手动:
.view(dtype)
2.2.1.3.3.1 投射
- 算术投射,简而言之:
- 只有类型(不是值!)操作符最重要
- 最大的“安全”模式能代表选出的两者
- 在一些情况下,数组中的量值可能“丢失”
- 在通用复制数据中的投射:
In [4]:
x = np.array([1, 2, 3, 4], dtype=np.float)
x
Out[4]:
array([ 1., 2., 3., 4.])
In [5]:
y = x.astype(np.int8)
y
Out[5]:
array([1, 2, 3, 4], dtype=int8)
In [6]:
y + 1
Out[6]:
array([2, 3, 4, 5], dtype=int8)
In [7]:
y + 256
Out[7]:
array([257, 258, 259, 260], dtype=int16)
In [8]:
y + 256.0
Out[8]:
array([ 257., 258., 259., 260.])
In [9]:
y + np.array([256], dtype=np.int32)
Out[9]:
array([257, 258, 259, 260], dtype=int32)
- 集合项目上的投射:数组的dtype在项目赋值过程中不会改变:
In [10]:
y[:] = y + 1.5
y
Out[10]:
array([2, 3, 4, 5], dtype=int8)
注意 具体规则:见文档:http://docs.scipy.org/doc/numpy/reference/ufuncs.html#casting-rules
2.2.1.3.3.2 再解释/视图
- 内存中的数据块(4字节)
0x01 || 0x02 || 0x03 || 0x04
- 4 of uint8, OR,
- 4 of int8, OR,
- 2 of int16, OR,
- 1 of int32, OR,
- 1 of float32, OR,
- ...
如何从一个切换另一个?
- 切换dtype:
In [11]:
x = np.array([1, 2, 3, 4], dtype=np.uint8)
x.dtype = "<i2"
x
Out[11]:
array([ 513, 1027], dtype=int16)
In [12]:
0x0201, 0x0403
Out[12]:
(513, 1027)
0x01 0x02 || 0x03 0x04
注意 little-endian:越不重要的字节在内存的左侧
- 创建新视图:
In [14]:
y = x.view("<i4")
y
Out[14]:
array([67305985], dtype=int32)
In [15]:
0x04030201
Out[15]:
67305985
0x01 0x02 0x03 0x04
注意:
.view()
创建视图,并不复制(或改变)内存块- 只改变dtype(调整数组形状):
In [16]:
x[1] = 5
In [17]:
y
Out[17]:
array([328193], dtype=int32)
In [18]:
y.base is x
Out[18]:
True
小练习:数据再解释
也可以看一下: view-colors.py
数组中的RGBA数据:
In [19]:
x = np.zeros((10, 10, 4), dtype=np.int8)
x[:, :, 0] = 1
x[:, :, 1] = 2
x[:, :, 2] = 3
x[:, :, 3] = 4
后三个维度是R、B和G,以及alpha渠道。
如何用字段名‘r’, ‘g’, ‘b’, ‘a’创建一个(10,10)结构化数组而不用复制数据?
In [ ]:
y = ...
assert (y['r'] == 1).all()
assert (y['g'] == 2).all()
assert (y['b'] == 3).all()
assert (y['a'] == 4).all()
答案
…
警告:另一个占有四个字节内存的数组:
In [21]:
y = np.array([[1, 3], [2, 4]], dtype=np.uint8).transpose()
x = y.copy()
x
Out[21]:
array([[1, 2],
[3, 4]], dtype=uint8)
In [22]:
y
Out[22]:
array([[1, 2],
[3, 4]], dtype=uint8)
In [23]:
x.view(np.int16)
Out[23]:
array([[ 513],
[1027]], dtype=int16)
In [24]:
0x0201, 0x0403
Out[24]:
(513, 1027)
In [25]:
y.view(np.int16)
Out[25]:
array([[ 769, 1026]], dtype=int16)
- 发生了什么?
- … 我们需要实际看一下x[0,1]里面是什么
In [26]:
0x0301, 0x0402
Out[26]:
(769, 1026)