¿Fue útil?

Solución

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()

Otros consejos

No estoy muy familiarizado con el formato PGM, pero en términos generales, solo usaría numpy.fromfile.fromfile comenzará en cualquier posición en la que se encuentre el puntero de archivo al que le pase, por lo que simplemente puede buscar (o leer) hasta el final del encabezado y luego usar fromfile para leer el resto.

Deberá utilizar infile.readline() en lugar 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))

En una nota al margen, el archivo "foo.pgm" que señaló en su comentario parece especificar el número incorrecto de filas en el encabezado.

Si va a leer muchos archivos que potencialmente tienen ese problema, puede rellenar la matriz con ceros o truncarla, así.

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))

De hecho, la 'cadena' después del encabezado es un binario en su archivo.Lo resolví a continuación (encontré lo siguiente: ndarray: [2047 2047 2047 ..., 540 539 539]) pero hay otro problema: el archivo no es lo suficientemente largo;cuenta solo 289872 números en lugar de 640 * 480 ...

Lamento muchísimo mi exageración al hacer una clase para ello ...

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: ¡gran idea de Joe Kington para llenar la imagen con ceros!

de aquí , entiendo que la información del encabezado se puede separar por espacios, carrodevoluciones u otros.Si el tuyo está separado por espacios (infórmame si no) puedes hacer:

with open('img.pgm') as f:
    lines = f.readlines()
    data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T

¡sus datos ahora son una matriz en formato int16!

Suponga que todavía está interesado en la información del encabezado, puede hacer:

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 pueda comparar los datos de la imagen con las líneas leídas:

assert (h.width, h.height) == data.shape    
assert h.maxval >= data.max()

Editar : si los datos de la imagen son binarios , el archivo debe abrirse como 'rb' y leerse después de la información del encabezado:

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')

Gracias a la respuesta de @ joe-kington por ayudarme a resolver esto.La solución sigue.

Hay un poco de trabajo adicional para no codificar la longitud conocida del encabezado (17 bytes en este caso), pero para determinarlo desde el encabezado.El estándar PGM dice que el encabezado generalmente termina con una nueva línea, pero puede terminar con cualquier espacio en blanco.Creo que este código se romperá en un PGM que usa espacios en blanco que no son de nueva línea para el delimitador de fin de encabezado.En este caso, el tamaño del encabezado estaría determinado por el tamaño de las variables que contienen ancho, alto y tamaño máximo, más dos bytes para 'P5', más 4 bytes de espacio en blanco.

Otros casos en los que esto podría romperse son si el ancho o el alto son más grandes que un int (imagen muy grande).O si el PGM es de 8 bits en lugar de 16 bits (que se puede determinar a partir de maxval y posible ancho, alto y el).

#!/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)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top