Numpy e PGM a 16 bit
Domanda
Qual è un modo efficiente e chiaro per leggere immagini PGM a 16 bit in Python con numpy?
Non posso usare PIL per caricare immagini PGM a 16 bit a causa di un bug PIL .Posso leggere nell'intestazione con il seguente codice:
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
Questo stampa i dati corretti: ('P5', ' ', '640', ' ', '480', ' ', '65535')
Ma ho la sensazione che non sia proprio il modo migliore.E oltre a ciò, ho problemi a capire come leggere i seguenti dati di x per y (in questo caso 640x480) per 16 bit con l'offset di size(header)
.
MODIFICA: IMMAGINE AGGIUNTA
Il codice MATLAB per leggere e visualizzare l'immagine è:
I = imread('foo.pgm');
imagesc(I);
E assomiglia a questo:
Soluzione
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()
Altri suggerimenti
Non ho molta familiarità con il formato PGM, ma in generale useresti solo numpy.fromfile
.fromfile
inizierà in qualunque posizione si trovi il puntatore del file a cui gli passi, quindi puoi semplicemente cercare (o leggere) alla fine dell'intestazione, quindi utilizzare fromfile
per leggere il resto.
Dovrai utilizzare infile.readline()
invece di 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))
In una nota a margine, il file "foo.pgm" a cui hai puntato nel tuo commento sembra specificare il numero sbagliato di righe nell'intestazione.
Se stai leggendo molti file che potenzialmente hanno questo problema, puoi semplicemente riempire l'array con zeri o troncarlo, in questo modo.
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))
In effetti, la "stringa" dopo l'intestazione è un binario nel tuo file.L'ho risolto di seguito (trovato quanto segue: ndarray: [2047 2047 2047 ..., 540 539 539]
) ma c'è un altro problema: il file non è abbastanza lungo;conta solo 289872 numeri invece di 640 * 480 ...
Sono terribilmente dispiaciuto per la mia esagerazione facendo un corso per questo ...
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
modifica: ottima idea di Joe Kington per riempire l'immagine di zeri!
da qui Capisco che le informazioni di intestazione possono essere separate da spazi, a caporitorni o altri.Se il tuo è separato da spazi (informami se diversamente) puoi fare:
with open('img.pgm') as f:
lines = f.readlines()
data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T
i tuoi dati ora sono un array in formato int16!
Supponi di essere ancora interessato alle informazioni di intestazione, puoi fare:
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])
in modo da poter confrontare i dati dell'immagine rispetto alle righe lette:
assert (h.width, h.height) == data.shape
assert h.maxval >= data.max()
Modifica : con i dati dell'immagine binari , il file deve essere aperto come "rb" e letto dopo le informazioni di intestazione:
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')
Grazie alla risposta di @ joe-kington per aver aiutato a capirlo.La soluzione segue.
C'è un po 'di lavoro extra per non codificare in modo rigido la lunghezza nota dell'intestazione (17 byte in formato questo caso), ma per determinarlo dall'intestazione.Lo standard PGM dice che l'intestazione di solito termina con una nuova riga ma può terminare con qualsiasi spazio bianco.Penso che questo codice si interromperà su un PGM che utilizza spazi vuoti non di nuova riga per il delimetro di fine intestazione.La dimensione dell'intestazione in questo caso sarebbe determinata dalla dimensione delle variabili che contengono larghezza, altezza e maxsize, più due byte per "P5", più 4 byte di spazio.
Altri casi in cui questo potrebbe non funzionare sono se la larghezza o l'altezza sono maggiori di un int (immagine molto grande).O se il PGM è a 8 bit anziché a 16 bit (che può essere determinato da maxval e possibile larghezza, altezza e dimensione del file).
#!/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)