Numpy и 16-разрядный PGM

что такое эффективный и понятный способ чтения 16-битных изображений PGM в Python с numpy?

Я не могу использовать PIL для загрузки 16-битных изображений PGM из-за ошибки PIL. Я могу прочитать в заголовке со следующим кодом:

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

это печатает правильные данные:('P5', ' ', '640', ' ', '480', ' ', '65535') но у меня такое чувство, что это не совсем лучший способ. И кроме того, у меня возникли проблемы с тем, как выяснить, как читать следующие данные x по y (в данном случае 640x480) 16-битным с помощью смещение size(header).

EDIT: ИЗОБРАЖЕНИЕ ДОБАВЛЕНО

код MATLAB для чтения и отображения изображения:

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

и выглядит так:

enter image description here

5 ответов


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

Я не очень знаком с форматом PGM, но, вообще говоря, вы просто используете numpy.fromfile. fromfile начнется с любой позиции указателя файла, который вы передадите ему, поэтому вы можете просто искать (или читать) в конце заголовка, а затем использовать fromfile читать остальное.

вам понадобится infile.readline() вместо 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))

на боковой ноте, " foo.PGM " файл, на который вы указали в своем комментарии, укажите неправильное количество строк в заголовке.

если вы собираетесь читать во многих файлах, которые потенциально имеют эту проблему, вы можете просто заполнить массив нулями или усечь его, как это.

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


действительно, эту строку после заголовка двоичного файла. Я решил, что ниже (нашел следующее: ndarray: [2047 2047 2047 ..., 540 539 539]) но есть еще одна проблема: файл недостаточно длинный; считает только 289872 числа вместо 640*480...

Я ужасно сожалею о моем exageration, сделав класс для него...

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: отличная идея от Джо Кингтона, чтобы заполнить изображение нулями!


С здесь Я понимаю, что информация заголовка может быть разделена пробелами, возвратами каретки или другими. Если ваш разделен пробелами (сообщите мне, если иначе), вы можете сделать:

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

ваши данные теперь массив в формате int16!

Предположим, вы все еще заинтересованы в информации заголовка, вы можете сделать:

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

, так что вы можете увидеть изображения данных от чтения строки:

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

редактировать: с данными изображениябинарные, файл должен быть открыт как "rb" и считан после информации заголовка:

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

благодаря ответу @joe-kington за помощь в этом. Решение следует.

есть немного дополнительной работы, чтобы не жестко кодировать известную длину заголовка (17 байт в этот случай), но определить его из заголовка. Стандарт PGM говорит, что заголовок обычно заканчивается новой строкой, но может заканчиваться любым пробелом. Я думаю, что этот код будет перерыв на ПГМ, что использует перевод строки пробел в конце заголовка разделитель. Размер заголовка в этом случае будет определяется размером переменных, содержащих ширину, высоту и maxsize, плюс два байта для "P5", плюс 4 байта пробелов.

другие случаи, когда это может нарушить, если ширина или высота больше, чем int (очень большая картинка). Или если PGM является 8-битным, а не 16-битным (который может быть определен из maxval и возможной ширины, высоты и размера файла).

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