Problem Solved: Using PIL with ImageMagick

Python    2012-09-28

I was tasked with a fairly simple project:

  • Traverse a directory with several hundred subdirectories, each containing up to half a dozen images of different sizes
  • If a directory already contained an image where [-8:] == "_175.jpg", just delete the entire directory, it was not needed
  • If the directory did not contain an image that size, look for either "_640.jpg" or "_260.jpg" and use one of those base images to create a new one named *"_175.jpg", resized down to 175x175 pixels
  • Normally, this would be an easy job for PIL. But I was working locally on an MBP, and like so many people my PIL install blew up at some point on the OS X upgrade timeline.

    Ironically, some of the PIL functionality still works. I was able to get at least as far as identifying and opening an image:

    	import PIL
    	from PIL import Image
    	
    	def resizefile(filename):
    	print "resize this file: " + filename
    	
    	# The output images need to be this width:
    	basewidth = 175
    	
    	# Use PIL to open the file:
    	img = Image.open(filename)
    	
    	# Determine the percentage to size down (in my case, width was the critical attribute):
    	width_percent = (basewidth / float(img.size[0]))
    	
    	# But PIL requires a height as well, as part of the 2-tuple size argument, 
    	# so we calculate that height proportional to the new width:
    	new_height = int((float(img.size[1]) * float(width_percent)))
    	
    	# And establish a name for the new file:
    	new_filename = filename[:-8]+"_175.jpg"
    	
    	# Here's the culprit: 
    	img = img.resize((basewidth, new_height), Image.ANTIALIAS)
    	img.save(new_filename)
    

    In hindsight, I never tested to see if the problem stems from the resize method, or the ANTIALIAS filter. But in any case, I got a stacktrace alerting me to a missing dependency:

    	Traceback (most recent call last):
    	  ...
    	  File "/Library/Python/2.7/site-packages/PIL/Image.py", line 37, in __getattr__
    		raise ImportError("The _imaging C module is not installed")
    	ImportError: The _imaging C module is not installed
    

    The PIL installation contains a "selftest" script that came back with the same dependency error:

    	[me@myenv Imaging-1.1.7]$ python selftest.py
    	*** The _imaging C module is not installed
    

    I wasted the better part of an evening trying to fix that annoying error, following advice from anyone that seemed credible. Apparently, this is a really common problem with PIL and newer versions of OS X:

    From Stack Overflow:

    The _imaging C module is not installed (June, 2010)

    Python: The _imagingft C module is not installed (October, 2010)

    _imaging C module error in python PIL (May, 2011)

    how to install PIL on mac os x 10.7.2 Lion (January, 2012)

    And then there's this:

    Man this has been an error that has been getting me down lately. [ApPel Freelance]

    In some cases moving to a newer version of PIL could help (although that was not the case for me). PyPI shows the latest version as 1.1.6, and that's what I was running, but 1.1.7 is available if you want to try that.

    In case libjpeg is your particular dependency issue, there is this very straightforward method of installing that first, before moving on to PIL:

    Mac OS X Lion: Install the Python Image Library (PIL) [Guillaume Piot]

    Although at least one user out there maintains that there needs to be a change made to the PIL setup.py, so a manual install (as opposed to using pip) is the answer:

    Python 2.7, OS X Lion and PIL, _imaging and Image [ApPel Freelance]

    His instructions are a little unclear - here they are from my command line history:

    	$ curl -O http://www.ijg.org/files/jpegsrc.v8c.tar.gz
    	$ tar -xvzf jpegsrc.v8c.tar.gz 
    	$ cd jpeg-8c/
    	$ sudo make clean
    	$ CC="gcc -arch i386" ./configure --enable-shared --enable-static
    	$ make
    	$ sudo make install
    	$ cd ../Imaging-1.1.6
    	$ sudo rm -rf build
    	$ vi setup.py
    (Insert this line: JPEG_ROOT = libinclude("/usr/local/lib"))
    	$ sudo python setup.py install
    

    Although I am on Lion, I took a look at this very similar instruction from Proteus Technologies that suggests uninstalling and reinstalling the existing libjpeg:

    Install PIL in Snow Leopard

    	$ curl -O http://www.ijg.org/files/jpegsrc.v8c.tar.gz
    	$ tar -xvzf jpegsrc.v8c.tar.gz
    	$ cd jpeg-8c
    	$ ./configure --enable-shared --enable-static
    	$ make
    	$ sudo make install
    
    	$ curl -O http://effbot.org/downloads/Imaging-1.1.6.tar.gz
    	$ tar zxvf Imaging-1.1.6.tar.gz
    	$ cd Imaging-1.1.6
    

    Needless to say, none of these manual install methods worked. So I decided to fall back to good old dependable pip:

    	$ sudo pip install PIL
    

    Nope.

    	[me@myenv Imaging-1.1.6]$ python selftest.py 
    	*** The _imaging C module is not installed
    

    "Aha!", I thought, "I'll just take to my Twitter feed! Surely some of my friends in the Python community have solved this problem before me!"

    And so, with the trusted advice of colleagues in my back pocket, I turned to what seems to be everyone's favorite solution these days: Pillow.

    Pillow, 'the "friendly" PIL fork', looks like a good solution for most people. I certainly had high hopes. The build instructions are clear, thorough, and address all of the necessary dependencies and multiple operating systems/versions of Python:

    Build instructions (all platforms) [PyPi]

    It didn't work for me, but I have had my Macbook for a very long time, and I have long suspected that in the deep, dark recesses of my operating system, some things are installed in places they ought not to be:

    	>>> import PIL
    	>>> import Image
    	>>> import _imaging
    	Traceback (most recent call last):
    	  File "", line 1, in 
    	  File "_imaging.py", line 7, in 
    		__bootstrap__()
    	  File "_imaging.py", line 6, in __bootstrap__
    		imp.load_dynamic(__name__,__file__)
    	ImportError: dlopen(./_imaging.pyd, 2): no suitable image found.  Did find:
    		./_imaging.pyd: unknown file type, first eight bytes: 0x4D 0x5A 0x90 0x00 0x03 0x00 0x00 0x00
    

    What the? I don't even.

    So it came down to this. I've used Python with ImageMagick before, and while it's not my tool of choice, it does work. In fact, resize commands are fairly straightforward:

    convert {original_file} -scale {width}x{height}! {new_file}

    So the new resize looks like this:

    	resize = str(basewidth)+"x"+str(new_height)+"!"
    	convert = subprocess.check_call(["convert", filename, "-scale", resize, new_filename])
    

    Where the subprocess issues a command something like this on every file:

    	convert milandvd4_300.jpg -scale 175x175! milandvd4_175.jpg
    

    At the end of the day, my hybrid method looks like this:

    	import PIL
    	from PIL import Image
    	import subprocess
    	
    	def resizefile(filename):
    	  print "resize this file: " + filename
    	
    	  basewidth = 175
    	
    	  # The PIL bit, because PIL is better at reading image dimensions anyway:
    	  img = Image.open(filename)
    	  width_percent = (basewidth / float(img.size[0]))
    	  new_height = int((float(img.size[1]) * float(width_percent)))
    	  new_filename = filename[:-8]+"_175.jpg"
    	
    	  # And the ImageMagick bit, because ImageMagick is what I have, and it works:
    	  resize = str(basewidth)+"x"+str(new_height)+"!"
    	  convert = subprocess.check_call(["convert", filename, "-scale", resize, new_filename])
    

    Whatever works, right?