Numpy и 16-битный PGM
Что такое эффективный и понятный способ чтения 16-битных изображений PGM в Python с помощью numpy?
Я не могу использовать PIL для загрузки 16-битных изображений PGM из-за ошибки PIL. Я могу прочитать в шапке со следующим кодом:
dt = np.dtype([('type', 'a2'),
('space_0', 'a1', ),
('x', 'a3', ),
('space_1', 'a1', ),
('y', 'a3', ),
('space_2', 'a1', ),
('maxval', 'a5')])
header = np.fromfile( 'img.pgm', dtype=dt )
print header
Это печатает правильные данные: ('P5', ' ', '640', ' ', '480', ' ', '65535')
Но у меня такое ощущение, что это не совсем лучший способ. И кроме того, у меня возникли проблемы, как выяснить, как читать в следующих данных x на y (в данном случае 640x480) на 16-битной со смещением size(header)
,
РЕДАКТИРОВАТЬ: ИЗОБРАЖЕНИЕ ДОБАВЛЕНО
MATLAB код для чтения и отображения изображения:
I = imread('foo.pgm');
imagesc(I);
И выглядит так:
5 ответов
import re
import numpy
def read_pgm(filename, byteorder='>'):
"""Return image data from a raw PGM file as numpy array.
Format specification: http://netpbm.sourceforge.net/doc/pgm.html
"""
with open(filename, 'rb') as f:
buffer = f.read()
try:
header, width, height, maxval = re.search(
b"(^P5\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups()
except AttributeError:
raise ValueError("Not a raw PGM file: '%s'" % filename)
return numpy.frombuffer(buffer,
dtype='u1' if int(maxval) < 256 else byteorder+'u2',
count=int(width)*int(height),
offset=len(header)
).reshape((int(height), int(width)))
if __name__ == "__main__":
from matplotlib import pyplot
image = read_pgm("foo.pgm", byteorder='<')
pyplot.imshow(image, pyplot.cm.gray)
pyplot.show()
Я не очень знаком с форматом PGM, но, вообще говоря, вы просто используете numpy.fromfile
, fromfile
начнется с любой позиции, в которой находится указатель файла, на который вы передаете, так что вы можете просто искать (или читать) конец заголовка, а затем использовать fromfile
читать остальное в.
Вам нужно будет использовать infile.readline()
вместо next(infile)
,
import numpy as np
with open('foo.pgm', 'r') as infile:
header = infile.readline()
width, height, maxval = [int(item) for item in header.split()[1:]]
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
Кстати, файл "foo.pgm", на который вы указали в своем комментарии, указывает на неправильное количество строк в заголовке.
Если вы собираетесь читать множество файлов, которые могут иметь такую проблему, вы можете просто заполнить массив нулями или усечь его, например, так.
import numpy as np
with open('foo.pgm', 'r') as infile:
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
image = np.fromfile(infile, dtype=np.uint16)
if image.size < width * height:
pad = np.zeros(width * height - image.size, dtype=np.uint16)
image = np.hstack([image, pad])
if image.size > width * height:
image = image[:width * height]
image = image.reshape((height, width))
Действительно, "строка" после заголовка - это двоичный файл в вашем файле. Я решил это ниже (нашел следующее: ndarray: [2047 2047 2047 ..., 540 539 539]
) но есть еще одна проблема: файл недостаточно длинный; насчитывает только 289872 номера вместо 640*480...
Я ужасно сожалею о своем преувеличении, сделав урок для него...
import numpy as np
import Image
class PGM(object):
def __init__(self, filepath):
with open(filepath) as f:
# suppose all header info in first line:
info = f.readline().split()
self.type = info[0]
self.width, self.height, self.maxval = [int(v) for v in info[1:]]
size = self.width * self.height
lines = f.readlines()
dt = [np.int8, np.int16][self.maxval > 255]
try:
# this will work if lines are integers separated by e.g. spaces
self.data = np.array([l.split() for l in lines], dtype=dt).T
except ValueError:
# data is binary
data = np.fromstring(lines[0], dtype=dt)
if data.size < size:
# this is the case for the 'db.tt/phaR587 (foo.pgm)'
#raise ValueError('data binary string probably uncomplete')
data = np.hstack((data, np.zeros(size-data.size)))
self.data = data[:size].reshape((self.width, self.height))
assert (self.width, self.height) == self.data.shape
assert self.maxval >= self.data.max()
self._img = None
def get_img(self):
if self._img is None:
# only executed once
size = (self.width, self.height)
mode = 'L'
data = self.data
self.img = Image.frombuffer(mode, size, data)
return self.img
Image = property(get_img)
mypgm = PGM('foo.pgm')
mypgm.Image
редактировать: отличная идея от Джо Кингтона, чтобы заполнить изображение нулями!
отсюда я понимаю, что информация заголовка может быть разделена пробелами, возвратами каретки или другими. Если ваш разделен пробелами (сообщите мне, если нет), вы можете сделать:
with open('img.pgm') as f:
lines = f.readlines()
data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T
ваши данные теперь массив в формате int16!
Предположим, что вы все еще заинтересованы в информации заголовка, вы можете сделать:
class Header(object):
def __init__(self, type, width, height, maxval):
self.type = type
self.width = int(width)
self.height = int(height)
self.maxval = int(maxval)
h = Header(*lines[0].split()[:4])
так что вы можете проверить данные изображения по прочитанным строкам:
assert (h.width, h.height) == data.shape
assert h.maxval >= data.max()
Редактировать: с данными изображения, являющимися двоичными, файл должен быть открыт как 'rb' и считан после информации заголовка:
import numpy as np
def as_array(filepath):
f = open(filepath, 'r')
w, h = size = tuple(int(v) for v in next(f).split()[1:3])
data_size = w * h * 2
f.seek(0, 2)
filesize = f.tell()
f.close()
i_header_end = filesize - (data_size)
f = open(filepath, 'rb')
f.seek(i_header_end)
buffer = f.read()
f.close()
# convert binary data to an array of the right shape
data = np.frombuffer(buffer, dtype=np.uint16).reshape((w, h))
return data
a = as_array('foo.pgm')
Спасибо ответу @joe-kington за помощь в выяснении этого. Решение следует.
Есть небольшая дополнительная работа, чтобы не жестко кодировать известную длину заголовка (в данном случае 17 байт), а определить ее из заголовка. Стандарт PGM гласит, что заголовок обычно заканчивается новой строкой, но может заканчиваться любым пробелом. Я думаю, что этот код сломается на PGM, который использует пробел без новой строки для разделителя конца заголовка. Размер заголовка в этом случае будет определяться размером переменных, содержащих ширину, высоту и максимальный размер, плюс два байта для "P5" плюс 4 байта пробела.
Другие случаи, когда это может нарушиться, это если ширина или высота больше, чем int (очень большое изображение). Или, если PGM 8-битный, а не 16-битный (который можно определить по maxval, а также по возможной ширине, высоте и размеру файла).
#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt
file='foo.pgm'
infile = open(file,'r')
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
infile.seek(len(header))
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
print width, height, maxval
plt.figimage(image)