2011-09-10 14 views
9

¿Cuál es una manera eficiente y clara de leer imágenes PGM de 16 bits en Python con numpy?Numpy y PGM de 16 bits

no puedo usar PIL para cargar imágenes PGM 16 bits due to a PIL bug. Puedo leer en el encabezado con el siguiente 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 

Esto imprime los datos correctos: ('P5', ' ', '640', ' ', '480', ' ', '65535') Pero tengo la sensación de que no es exactamente la mejor manera. Y más allá de eso, tengo problemas para averiguar cómo leer en los siguientes datos de x por y (en este caso, 640x480) en 16 bits con el desplazamiento de size(header).

EDIT: la imagen Añadido

código MATLAB para leer y mostrar la imagen es:

I = imread('foo.pgm'); 
imagesc(I); 

y se parece a esto:

enter image description here

+0

puede conectar un img.pgm ejemplo? Fuera de tema: revisó su sitio; es posible que le guste ver [esto] (http://www.bbc.co.uk/news/science-environment-14803840): parece que usted no es el único que busca agua más cálida en el Ártico ... (evidencia de apoyo para su (coleages) tesis tal vez?) – Remi

+0

PGM aquí: http://db.tt/phaR587 PD Uno no tiene que buscar mucho para encontrar estas cosas ... :(. – mankoff

Respuesta

17
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() 
+0

Muy bueno, pero en el caso de este archivo de prueba, '> u2' produce los valores incorrectos (rango 4098 a 65287) mientras que' u2' produce los valores correctos (528 a 2047). Mencionas a big-endian en otro comentario. Los datos fueron producidos en y estoy leyendo en un chip Intel (little endian). Supongo que fue escrito en formato nativo. – mankoff

+0

La especificación dice "El byte más significativo es el primero", que es big endian. Ver también http://en.wikipedia.org/wiki/Netpbm_format#16-bit_extensions. – cgohlke

+0

Matlab lee los datos como big endian, por lo que la imagen que se muestra en su pregunta sería incorrecta (?). Siempre puede intercambiar los bytes más adelante si está leyendo un archivo no estándar. – cgohlke

1

de here entiendo que la información del encabezado se puede separar por espacios, carro devoluciones u otros. Si el suyo es separado por espacios (informarme si de otra manera) que puede 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 es ahora una matriz en formato Int16!

Supongamos que usted todavía está interesado en la información del encabezado, que 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 comprobar los datos de imagen en contra de las líneas de lectura:

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

Editar: con siendo binaria los datos de imagen, el archivo tiene que ser abierto como 'rb' y leer 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') 
+0

Creo que el enlace que adjuntó describe mi formato correctamente. Sin embargo, tengo el formato P5 "en bruto" (el más común, descrito primero) El encabezado es ASCII, pero los datos a continuación son binarios, y parece que 'readlines()' está fallando debido a esto. – mankoff

+0

Right. Readlines() lee una línea, pero la interpretación de esa línea tiene que ser vía np. fromstring(), o, como tú y Joe Kington proponen, directamente con np.fromfile() ya que sabes que es binario de todos modos. Sin embargo, hay otro problema: ver mi segunda respuesta – Remi

3

no estoy terriblemente familiarizado con el formato PGM, pero hablando en general, que acaba tendría que utilizar numpy.fromfile. fromfile comenzará en la posición en la que esté el puntero al archivo, de modo que puede buscar (o leer) hasta el final del encabezado y luego usar fromfile para leer el resto.

Necesitará para usar 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, el archivo "foo.pgm" usted ha señalado en su comentario aparece para especificar el número incorrecto de filas en el encabezado.

Si usted va a estar leyendo en una gran cantidad de archivos que potencialmente tienen ese problema, sólo puede rellenar la matriz con ceros o truncar que, como este.

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

+0

bastante elegante, ¡y funciona para mankoffs binary! I only obtuve un resultado extraño cuando lo probaba en un archivo pgm con formato de cadena estándar ... – Remi

+0

@Remi - Sí, no tenía la intención de un archivo ascii pgm. Sin embargo, es bastante simple de usar 'np.loadtxt' o algo similar en ese caso. –

+0

Cerca pero aún un insecto. El archivo tiene 614417 bytes de longitud, que es igual a 640 * 480 * 2 + 17, que es un encabezado de 17 bytes y datos de 640x480 de dos bytes (16 bits). La imagen se muestra correctamente decodificada como tal manualmente en otros idiomas (IDL) y usando rutinas incorporadas en otros lugares (GIMP, MATLAB). Voy a publicar una versión de la imagen en la pregunta en breve. Perdón por no haber proporcionado toda esta información inicialmente, estoy averiguando también a medida que avanzo ... – mankoff

1

De hecho, la 'cadena' después de la cabecera 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 sólo 289.872 números en lugar de 640 * 480 ...

me siento mucho por mi exageración al hacer una clase a que ...

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 

de edición: gran idea de Joe Kington para llenar la imagen con ¡ceros!

+0

El archivo ** es ** lo suficientemente largo. Creo que 'readline()' está leyendo demasiado. Tal vez algunos de los binarios están en la primera línea también? – mankoff

0

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

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

Otros casos donde esto podría romperse son si el ancho o la altura son más grandes que una 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 tamaño del archivo).

#!/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) 
+0

¡Felicidades, muy suave! Debería haber ido a la cama todo el tiempo, supongo ... – Remi

+2

El dtype debe ser big endian. – cgohlke

Cuestiones relacionadas