2011-09-10 5 views
9

numpyで16ビットのPGMイメージをPythonで読み込むには効率的で明確な方法はありますか?ナンシーと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')をしかし、私は非常に最良の方法ではない気がします。そしてそれを越えて、yの次のデータ(ここでは640x480)を、オフセットがsize(header)の16ビットでどのように読み取るかを調べるには問題があります。

EDIT:IMAGEは、画像を読み込み、表示する

MATLABコードを追加されます。

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

そして、このようになります:私はあることを理解しhereから

enter image description here

+0

あなたは例のimg.pgmを添付することができますか?トピックオフ:あなたのサイトをチェックしました。あなたは[この](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)を生成します。他のコメントでビッグエンディアンを言います。データは、Intel(リトルエンディアン)チップで読み込まれています。私はそれがネイティブフォーマットで書かれていたと推測します。 – mankoff

+0

仕様では、「最上位バイトが先頭です。」というビッグエンディアンです。 http://en.wikipedia.org/wiki/Netpbm_format#16-bit_extensionsも参照してください。 – cgohlke

+0

Matlabはデータをビッグエンディアンとして読み込み、質問に表示される画像が間違っています(?)。非標準ファイルを読み込んでいる場合は、後でいつでもバイトをスワップできます。 – cgohlke

1

ヘッダー情報はスペース、キャリッジ返品その他。あなたは、あなたが行うことができ、スペース(そうでないなら、私を知らせる)で区切られている場合:

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()は1行を読み込みますが、その行の解釈はnp経由でなければなりません。 fromstring()、またはあなたとJoe Kingtonのように、np.fromfile()を使って直接バイナリであることを知っているので、直接質問することもできますが、もう1つの問題があります: – Remi

3

私はPGM形式にはあまり馴染みがありませんが、一般的にはnumpy.fromfileを使用しています。 fromfileはどんな位置あなたは、単に求める(または読み取り)のヘッダの最後に、その後に、残りを読むためにfromfileを使用することができますので、あなたがそれに渡すファイルポインタが、であるから開始します。

あなたが必要となりますnext(infile)の代わりにinfile.readline()を使用してください。サイドノートでは

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」ファイルは、ヘッダ内の行の間違った番号を指定することが表示されます。

この問題が発生する可能性のあるファイルをたくさん読んでいる場合は、配列に0を埋め込むか、またはこのように切り捨てることができます。

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バイトのヘッダーと640×480の2バイト(16ビット)データです。イメージは、他の言語(IDL)で手動で正しくデコードされ、組み込みルーチンを別の場所(GIMP、MATLAB)で使用して正しく表示されます。私はすぐに質問の画像のバージョンを投稿します。最初にこの情報をすべて提供してくれなかったのは申し訳ありませんが、私が行っている間に私はそれを理解しています... – mankoff

1

確かに、ヘッダの後に 'string' は、ファイル内のバイナリです。私はそれを解決しました(以下を見つけました: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

感謝。解決策は次のとおりです。

既知のヘッダーの長さ(この場合は では17バイト)をハードコードしないで、ヘッダーからヘッダーの長さを決定するという少しの余分な作業があります。 PGM標準によれば、ヘッダは通常改行で終わりますが、空白で終わることがあります。私は、このコードは、改行の終わりの区切り文字に改行以外の空白を使用するPGMを打ち破ると思います。この場合のヘッダーサイズは、幅、高さ、および最大サイズを保持する変数のサイズと、 'P5'の2バイトに4バイトの空白を加えたもので決まります。幅または高さがint(非常に大きな画像)よりも大きい場合には、これが壊れるかもしれません

他の例です。 PGMが16ビットではなく8ビット(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

関連する問題