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:

enter image description here

Était-ce utile?

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top