"""
Tiny library for reading portable gray map (PGM) files.

The grayscale image is read into a numeric array of type UInt8 or
UInt16 (depending on whether the maximum value is less than 256) and
shape (height, width).

See http://netpbm.sourceforge.net/doc/pgm.html for more information
about the PGM format. This library only reads the 'raw' PGM format,
not the 'plain' one.
"""

__metaclass__ = type

import re, numarray
from numarray import fromstring, UInt16, UInt8

_nonspace = re.compile('\\S+')

class InvalidImage(Exception): pass

def _next_token(data, pos):
    while True:
        match = _nonspace.search(data, pos)
        text, pos = match.group(0), match.end()
        if not text.startswith('#'):
            break
        pos = data.index('\n', pos) + 1
    return int(text), pos

class Image:

    def __init__(self, data):
        try:
            self._parse(data)
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            raise InvalidImage(e)

    def _get_maxval(self):
        return self._maxval

    maxval = property(_get_maxval)

    def _get_pixels(self):
        return self._pixels

    pixels = property(_get_pixels)

    def _parse(self, data):
        assert data.startswith('P5')
        pos = 2
        self._width, pos = _next_token(data, pos)
        self._height, pos = _next_token(data, pos)
        self._maxval, pos = _next_token(data, pos)
        pixels = data[pos+1:]
        assert 0 < self._maxval < 65536
        shape = (self._height, self._width)
        if self._maxval < 256:
            typeobj = UInt8
        else:
            typeobj = UInt16
        self._pixels = fromstring(pixels, typeobj, shape)
