PGM Numpy et 16 bits
Question
Quelle est une façon efficace et claire de lire des images PGM 16 bits en python avec Numpy?
Je ne peux pas utiliser PIL pour charger des images PGM 16 bits En raison d'un bug PIL. Je peux lire dans l'en-tête avec le code suivant:
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
Cela imprime les données correctes: ('P5', ' ', '640', ' ', '480', ' ', '65535')
Mais j'ai le sentiment qui n'est pas tout à fait le meilleur moyen. Et au-delà, j'ai du mal à comprendre comment lire dans les données suivantes de x par y (dans ce cas 640x480) par 16 bits avec le décalage de size(header)
.
Edit: image ajoutée
Le code MATLAB pour lire et afficher l'image est:
I = imread('foo.pgm');
imagesc(I);
Et ressemble à ceci:
La solution
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()
Autres conseils
Je ne suis pas terriblement familier avec le format PGM, mais d'une manière générale, vous utiliseriez juste numpy.fromfile
. fromfile
commencera à n'importe quelle position que le pointeur de fichier que vous passez est, afin que vous puissiez simplement chercher (ou lire) jusqu'à la fin de l'en-tête, puis utiliser fromfile
pour lire le reste.
Vous devrez utiliser infile.readline()
à la place 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))
Sur une note latérale, le fichier "foo.pgm" que vous avez indiqué dans votre commentaire semble spécifier le mauvais numéro de lignes dans l'en-tête.
Si vous allez lire dans de nombreux fichiers qui ont potentiellement ce problème, vous pouvez simplement remplir le tableau avec des zéros ou le tronquer, comme ceci.
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))
En effet, la «chaîne» après l'en-tête est un binaire dans votre fichier. J'ai résolu cela ci-dessous (trouvé ce qui suit: ndarray: [2047 2047 2047 ..., 540 539 539]
) Mais il y a un autre problème: le fichier n'est pas assez long; ne compte que 289872 numéros au lieu de 640 * 480 ...
Je suis terriblement désolé pour mon exagération en faisant un cours pour ça ...
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
Edit: Excellente idée de Joe Kington pour remplir l'image avec des zéros!
de ici Je comprends que les informations d'en-tête peuvent être séparées par des espaces, des retours de chariot ou d'autres. Si le vôtre est séparé par des espaces (informez-moi si autrement) vous pouvez faire:
with open('img.pgm') as f:
lines = f.readlines()
data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T
Vos données sont maintenant un tableau au format INT16!
Supposons que vous soyez toujours intéressé par les informations de l'en-tête, vous pouvez faire:
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])
Pour que vous puissiez vérifier les données d'image par rapport aux lignes de lecture:
assert (h.width, h.height) == data.shape
assert h.maxval >= data.max()
Éditer: avec les données d'image étant binaire, le fichier doit être ouvert en tant que «RB» et lu après les informations de l'en-tête:
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')
Merci à la réponse de @ Joe-Kington pour avoir aidé à comprendre cela. La solution suit.
Il y a un peu de travail supplémentaire pour ne pas coder dur la longueur de l'en-tête connue (17 octets dans ce cas), mais pour le déterminer de l'en-tête. La norme PGM dit que l'en-tête se termine généralement par une nouvelle ligne mais peut se terminer par n'importe quel espace. Je pense que ce code se cassera sur un PGM qui utilise un espace blanc non neuf pour le délimiter de fin de tête. La taille de l'en-tête dans ce cas serait déterminée par la taille des variables de la largeur, de la hauteur et du maxsize, plus deux octets pour «p5», plus 4 octets d'espace.
D'autres cas où cela pourrait se casser est si la largeur ou la hauteur est plus grande qu'un int (très grande image). Ou si le PGM est 8 bits plutôt que 16 bits (qui peut être déterminé à partir de MaxVal, et une largeur possible, la hauteur et la taille des fichiers).
#!/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)