How to write a PIL image filter for a simple pgm format?

How to write a filter for python image library for pgm plain ascii format (P2). The problem here is that the main PIL filter assumes a constant number of bytes per pixel.

My goal is to open feep.pgm with Image.open (). See http://netpbm.sourceforge.net/doc/pgm.html or below.

An alternative solution is that I find another well-documented ascii grayscale format that is supported by PIL and all major graphics programs. Any suggestions?

feep.pgm:

P2 # feep.pgm 24 7 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

edit: Thanks for the answer, it works ... but I need a solution that uses Image.open (). Most python programs use PIL to manipulate graphics (google: python image open). Thus, I need to be able to register a filter for PIL. Then I can use any software using PIL. Now I think mostly scipy, pylab, etc. Dependent programs.

edit Ok, I think I get it now. Below is the pgm2pil.py wrapper:

 import Image import numpy def pgm2pil(filename): try: inFile = open(filename) header = None size = None maxGray = None data = [] for line in inFile: stripped = line.strip() if stripped[0] == '#': continue elif header == None: if stripped != 'P2': return None header = stripped elif size == None: size = map(int, stripped.split()) elif maxGray == None: maxGray = int(stripped) else: for item in stripped.split(): data.append(int(item.strip())) data = numpy.reshape(data, (size[1],size[0]))/float(maxGray)*255 return numpy.flipud(data) except: pass return None def imageOpenWrapper(fname): pgm = pgm2pil(fname) if pgm is not None: return Image.fromarray(pgm) return origImageOpen(fname) origImageOpen = Image.open Image.open = imageOpenWrapper 

There is a small update for misha's answer. Image.open must be saved to prevent endless contours. If pgm2pil returns None, then the wrapper calls pgm2pil, which returns None, which calls pgm2pil ...

The following is a test function (feep_false.pgm is the wrong pgm, for example, "P2" → "FOO", and lena.pgm is just an image file ):

 import pgm2pil import pylab try: pylab.imread('feep_false.pgm') except IOError: pass else: raise ValueError("feep_false should fail") pylab.subplot(2,1,1) a = pylab.imread('feep.pgm') pylab.imshow(a) pylab.subplot(2,1,2) b = pylab.imread('lena.png') pylab.imshow(b) pylab.show() 
+4
source share
1 answer

The way I'm doing this right now is numpy :

  • Reading an image into a 2D numpy array. You don't need to use numpy , but I found it easier to use than regular Python 2D arrays
  • Convert a numpy 2D array to a PIL.Image object using PIL.Image.fromarray

If you insist on using PIL.Image.open , you can write a shell that first tries to load the PGM file (by looking at the header). If it is PGM, upload the image using the steps above, otherwise just PIL.Image.open responsibility at PIL.Image.open .

Here is the code that I use to get a PBM image into a numpy array.

 import re import numpy def pbm2numpy(filename): """ Read a PBM into a numpy array. Only supports ASCII PBM for now. """ fin = None debug = True try: fin = open(filename, 'r') while True: header = fin.readline().strip() if header.startswith('#'): continue elif header == 'P1': break elif header == 'P4': assert False, 'Raw PBM reading not implemented yet' else: # # Unexpected header. # if debug: print 'Bad mode:', header return None rows, cols = 0, 0 while True: header = fin.readline().strip() if header.startswith('#'): continue match = re.match('^(\d+) (\d+)$', header) if match == None: if debug: print 'Bad size:', repr(header) return None cols, rows = match.groups() break rows = int(rows) cols = int(cols) assert (rows, cols) != (0, 0) if debug: print 'Rows: %d, cols: %d' % (rows, cols) # # Initialise a 2D numpy array # result = numpy.zeros((rows, cols), numpy.int8) pxs = [] # # Read to EOF. # while True: line = fin.readline().strip() if line == '': break for c in line: if c == ' ': continue pxs.append(int(c)) if len(pxs) != rows*cols: if debug: print 'Insufficient image data:', len(pxs) return None for r in range(rows): for c in range(cols): # # Index into the numpy array and set the pixel value. # result[r, c] = pxs[r*cols + c] return result finally: if fin != None: fin.close() fin = None return None 

You will have to modify it a little to fit your goals, namely:

  • Deal with P2 (ASCII, grayscale) instead of P1 (ASCII, bilevel).
  • Use a different container if you are not using numpy. Normal Python 2D arrays will work fine.

EDIT

Here is how I can process the shell:

 def pgm2pil(fname): # # This method returns a PIL.Image. Use pbm2numpy function above as a # guide. If it can't load the image, it returns None. # pass def wrapper(fname): pgm = pgm2pil(fname) if pgm is not None: return pgm return PIL.Image.open(fname) # # This is the line that "adds" the wrapper # PIL.Image.open = wrapper 

I did not write pgm2pil because it will be very similar to pgm2numpy . The only difference is that it stores the result in PIL.Image and not in the numpy array. I also have not tested the shell code (sorry, a bit short in time at the moment), but this is a fairly common approach, so I expect it to work.

Now it seems that you want to use other applications that use PIL to upload images in order to be able to process PGM. This is possible using the above approach, but you must be sure that the above wrapper code is added before the first call to PIL.Image.open . You can verify that this happens by adding shell source code to the PIL source code (if you have access).

+5
source

All Articles