mlindgren.ca

– 🕓 5 min read

The state of Python imaging

(With apologies to Eevee, of whom I am a great fan.)

So, I'm working on a photo album app written in Python.  I'm using Python because, well, I love it.  I think it's the perfect language for web development; it's simple and very readable, and ample syntactic sugar and just the right mix of procedural and functional features almost completely eliminate tedious boilerplate.  As a trivial example for those who have never used Python, consider:

db_tags = dict([(tag.name, tag) for tag in
                db.session.query(db.MetadataTag).filter_by(source = 'exif')])

What I'm doing here is grabbing all of the EXIF tags I know about out of my database with SQLAlchemy (also awesome, by the way), and creating a dictionary which maps each tag's name to the corresponding ORM object; I can find the ORM object simply with db_tags[tag_name], which lets me easily and efficiently insert new tag values as I read EXIF data from a photo.  This isn't particularly difficult to do in any other language; as I said, it's a trivial example.  But consider how much more verbose the code would be: PHP, which is still the most popular language for web applications despite being an incorrigible pile of garbage, doesn't have list comprehensions.  I don't even want to think about how many lines of code this would take in Java; additionally, while I'm unfamiliar with them in general, I suspect Java ORMs require the use of generics to a painful extent (i.e. more than not at all).  In Python it only takes one SLOC, without sacrificing any readability.12

So, Python's pretty great. But like all languages, it does have a few problems. The one I have had the most difficulty with in my current project is that third party library support is somewhat lacking in some important areas; specifically, there aren't many good options for reading and manipulating images. The most popular library seems to be the Python Imaging Library, PIL. It's easy enough to install and provides a passable set of core features, but doesn't go much beyond that.  For instance, none of the various camera raw formats seem to be supported, which I consider a fairly important feature for my photo album; I'd very much like be able to upload original raws and have the application automatically convert them to JPEGs for me.  Additionally, it doesn't provide much in the way of convenience methods for cropping and resizing images.  ImageMagick has a very nice means of specifying cropping and resizing geometry with various aspect ratio-preserving (or not) behaviour, and I wanted to be able to leverage something similar for my app so that I could quickly prototype with various size options.

Given that ImageMagick provides most of the functionality I want that PIL lacks, it seemed like a good option, but it's a C library and Python doesn't yet have mature bindings for it.  It's getting there, though!  I decided to go with Wand, which is a fork of python-magickwand.  It has a nice high-level API, but also provides direct access to most of the low-level MagickWand APIs, making it fairly flexible.  When I first found it, it did not have support for the nice geometry specifications I wanted to use, but being an open source project I was able to patch that in.  Version 0.2.2 will include my patch, which provides the Image.transform method for easy cropping and resizing.  Eevee is also working on a Python imaging library, which is promising, but it's still a work in progress.

Unfortunately Wand doesn't yet support retrieving EXIF, IPTC or XMP metadata from images, which is another requirement for me.  ImageMagick does support EXIF at least, and that's on the roadmap for Wand, but implementing a high-level interface for EXIF tags is a bigger task than I have time for at the moment.  That meant that I had to turn to another library to retrieve metadata.  pyexiv2, a Python binding for the exiv2 C++ library, initially looked promising.  Unfortunately I discovered that it's not installable via any Python package manager; it must be built from source (via a build system I've never even heard of, and I'm familiar with more than a few build systems), unless a binary is provided for your platform by the project maintainers (i.e. you are on Debian, Ubuntu, Fedora or Windows).

My project already has more dependencies than I'm entirely comfortable with; one of the reasons that Python only has modest adoption as a web application platform despite being a great language for the task is that it doesn't have a kitchen sink-style standard library, and third-party libraries require separate installation.  In general I consider that a good thing; I'd rather have a well-designed standard library than a gigantic mishmash of inconsistently named global functions.  However, it does make deployment a little more complex.  So far I've managed to stick to libraries which can be installed in one step using well-known package managers, so the setup procedure for my app should not be too arduous.  Adding pyexiv2 as a dependency and thereby including an entirely different build system in the process would likely have complicated the installation procedure to the point that no actual ordinary humans would ever use my application.  That's something I want to avoid, if possible.

For now I have settled for exif-py.  It only handles EXIF tags, not IPTC or XMP, which is disappointing because I really wanted to support multiple metadata formats.  It's a pure Python library in a single file, though, so I can just include with my distribution.  It's easy to use and gets the job done, so it will have to do for now.  I'm hoping that pyexiv2 will show up in pip (a Python package manager) at some point, though.  [Updated September 16, 2012: I've discovered that exif-py is broken in at least a couple significant ways, so I'm kind of back at square one for handling metadata.  exif-py seems to be unable to detect EXIF data in some files that have it.  Additionally, the strings that it translates orientation codes to are not consistent with each other, so they're basically wrong.  I've informed the maintainers of these issues, but I don't have time to fix them myself.]

The process of trying to find a good imaging package for Python and ultimately settling for two separate libraries which still don't quite do everything I want has made it clear to me that third party library support for Python still has a way to go.  Hopefully the situation will improve as adoption of the language increases.  I'm happy to do my part, both through contributions to open source libraries such as Wand, and by building apps using Python to bolster the ecosystem.  I hope that by demonstrating what's possible with Python as a web application platform, I can help convince new web developers to build their applications using Python as well... which provides yet more reason for me to be diligent about finishing my photo album app!


  1. Okay, the dict function is perhaps not the most explicit. Check the docs once, though, and it's very easy to understand. 

  2. Ruby would doubtless be similarly concise, and it seems like a nice language in my limited experience, but I find its syntax and distinction between "symbols" and strings to not be to my taste. 

Comments