Numpy e PGM de 16 bits
Pergunta
Qual é uma maneira eficiente e clara de ler imagens PGM de 16 bits em Python com numpy?
Não consigo usar PIL para carregar imagens PGM de 16 bits devido a um bug de PIL .Posso ler no cabeçalho com o seguinte código:
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
Isso imprime os dados corretos: ('P5', ' ', '640', ' ', '480', ' ', '65535')
Mas tenho a sensação de que não é a melhor maneira.E além disso, estou tendo problemas para descobrir como ler os seguintes dados de x por y (neste caso 640x480) por 16 bits com o deslocamento de size(header)
.
EDITAR: IMAGEM ADICIONADA
O código MATLAB para ler e exibir a imagem é:
I = imread('foo.pgm');
imagesc(I);
E tem a seguinte aparência:
Solução
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()
Outras dicas
Não estou muito familiarizado com o formato PGM, mas de modo geral, você apenas usaria numpy.fromfile
.fromfile
começará na posição em que o ponteiro do arquivo que você passar para ele estiver, então você pode simplesmente procurar (ou ler) até o final do cabeçalho e usar fromfile
para ler o resto.
Você precisará usar infile.readline()
em vez de 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))
Em uma observação lateral, o arquivo "foo.pgm" que você apontou em seu comentário parece especificar o número errado de linhas no cabeçalho.
Se for ler muitos arquivos que potencialmente têm esse problema, você pode apenas preencher a matriz com zeros ou truncá-la, assim.
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))
Na verdade, a 'string' após o cabeçalho é um binário em seu arquivo.Resolvi isso abaixo (encontrei o seguinte: ndarray: [2047 2047 2047 ..., 540 539 539]
) mas há outro problema: o arquivo não é longo o suficiente;conta apenas 289872 números em vez de 640 * 480 ...
Lamento muito o meu exagero em fazer uma aula para isso ...
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
editar: ótima ideia de Joe Kington para preencher a imagem com zeros!
de aqui , entendo que as informações do cabeçalho podem ser separadas por espaços ou por carrodevoluções ou outros.Se o seu estiver separado por espaços (me informe se o contrário), você pode fazer:
with open('img.pgm') as f:
lines = f.readlines()
data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T
seus dados agora são uma matriz no formato int16!
Suponha que você ainda esteja interessado nas informações do cabeçalho, você pode fazer:
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])
para que você possa comparar os dados da imagem com as linhas lidas:
assert (h.width, h.height) == data.shape
assert h.maxval >= data.max()
Editar : com os dados da imagem sendo binários , o arquivo deve ser aberto como 'rb' e lido após as informações do cabeçalho:
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')
Obrigado à resposta de @joe-kington por ajudar a descobrir isso.A solução segue.
Há um pouco de trabalho extra para não codificar o comprimento de cabeçalho conhecido (17 bytes em neste caso), mas para determiná-lo a partir do cabeçalho.O padrão PGM diz que o cabeçalho geralmente termina com uma nova linha, mas pode terminar com qualquer espaço em branco.Acho que esse código vai quebrar em um PGM que usa espaços em branco que não sejam de nova linha para o delimitador de fim de cabeçalho.O tamanho do cabeçalho, neste caso, seria determinado pelo tamanho das variáveis contendo largura, altura e tamanho máximo, mais dois bytes para 'P5', mais 4 bytes de espaço em branco.
Outros casos em que isso pode falhar são se a largura ou a altura forem maiores do que um inteiro (imagem muito grande).Ou se o PGM for de 8 bits em vez de 16 bits (que pode ser determinado a partir de maxval e possível largura, altura e tamanho do arquivo).
#!/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)