2011-09-10 73 views
9

什麼是有效和清晰的方式來讀取Python中的16位PGM圖像與numpy?Numpy和16位PGM

我不能使用PIL來加載16位PGM圖像due to a PIL bug。我可以在頭下面的代碼閱讀:

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')但我有一種感覺,是不是很最好的方式。除此之外,我很難找出如何通過16位的偏移量size(header)來讀取x(y)(本例中爲640x480)的以下數據。

編輯:圖片已新增

MATLAB代碼讀取和顯示的圖像是:

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

,看起來像這樣:

enter image description here

+0

可以附加一個例子img.pgm? Off topic:檢查你的網站;你可能想看到[this](http://www.bbc.co.uk/news/science-environment-14803840):似乎你並不是唯一在北極周圍尋找溫暖的水的人(支持證據爲您的(coleages)論文或許?) – Remi

+0

PGM here:http://db.tt/phaR587 PS ( – mankoff

回答

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

非常好,但是在這個測試文件的情況下,'u2'產生錯誤的值(範圍4098到65287),而'u2'產生正確的值(528到2047)。你在另一個評論中提到了大前鋒。數據是在英特爾(小端)芯片上製作的,我正在閱讀。我認爲它是用原生格式編寫的。 – mankoff

+0

規範說「最重要的字節是第一位」,這是一個大端。另請參閱http://en.wikipedia.org/wiki/Netpbm_format#16-bit_extensions。 – cgohlke

+0

Matlab將數據讀取爲大端,因此您問題中顯示的圖像將錯誤(?)。如果您正在閱讀非標準文件,則可以隨後交換字節。 – cgohlke

1

here我明白標題信息可以由空格,馬車分隔退貨或其他。如果你的空格分開(否則通知我)你可以這樣做:

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

)我認爲你附加的鏈接正確地描述了我的格式,但是我有P5「原始」格式(更常見的格式,首先描述)頭文件是ASCII碼,但下面的數據是二進制的,看起來'readlines()'因此失敗了。 – mankoff

+0

Right。readlines()讀取一行,但該行的解釋必須通過np。 fromstring(),或者像你和Joe Kington所建議的那樣,直接使用np.fromfile(),因爲你知道它是二進制的,但還有另一個問題:看到我的第二個回答 – Remi

3

我對PGM格式並不熟悉,但一般來說你只需要使用numpy.fromfilefromfile將從您傳遞給它的文件指針的任何位置開始,因此您可以簡單地查找(或讀取)到標題末尾,然後使用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)) 

+0

非常優雅,適用於mankoffs二進制!當在一個標準的字符串格式化的pgm文件上測試它時,得到了奇怪的輸出... – Remi

+0

@Remi - 是的,我不打算將它用於ascii pgm文件,但使用'np.loadtxt'或類似的東西很簡單這種情況。 –

+0

關閉但仍然一個錯誤。該文件長度爲614417字節,等於640 * 480 * 2 + 17,這是一個17字節的標題和一個640x480的2字節(16位)的數據。圖像以其他語言(IDL)手動顯示並在其他地方(GIMP,MATLAB)使用內置例程正確解碼。我將很快在問題中發佈圖像版本。對不起,最初沒有提供所有這些信息,我正在弄清楚,因爲我去... – mankoff

1

事實上,在頭後的 '串' 是在文件中的二進制文件。我解決了下面的問題(發現如下: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 

編輯:喬金頓偉大的想法,以填補圖像與零!

+0

文件**足夠長。我認爲'readline()'讀得太多了。也許一些二進制文件也在第一行? – mankoff

0

感謝@ joe-kington的幫助解決這個問題。解決方案如下。

有額外的工作不硬編碼一點點已知的頭長度(17個字節在 這種情況下),但是從標題確定它。 PGM標準說頭通常以換行符結束,但可以以任何空格結束。我認爲這段代碼將在一個PGM上打破,該PGM使用非換行符空白符作爲報頭結尾的分隔符。在這種情況下,標題大小將由包含寬度,高度和最大大小的變量的大小,以及'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) 
+0

恭喜,非常順利!應該一直睡覺,我猜... – Remi

+2

dtype應該是大端。 – cgohlke