As we’ve seen, the Arduino sketch uses massive tables of precomputed stuff. These tables are generated with a script written in Python, using image files as inputs.

To generate new tables, you need some familiarity with Python and command-line script usage. Your computer must have Python installed, plus the Python Imaging Library. That’s going to vary on different systems and is beyond the scope of this guide, but Googling will turn up some getting-started resources.

So we’re singing from the same page, let’s lay out some eye terminology…

The “white” of the eye is known as the sclera.

The iris is the muscle that contracts to adjust the size of the pupil in response to light.

The upper and lower eyelids are involved in blinking.

Eyes are weirder than you might think. They’re not simply football-shaped, they’ve got two lids, and the pupil and eyelids interact. The Arduino sketch takes a few shortcuts, but makes an effort at simulating these effects.

The Python script (located in the “convert” folder from the Github repository) — — takes six or seven images (corresponding to eye parts above) as inputs and generates a table for each. There’s also an array that it generates on its own — it precomputes and stores a bunch of trigonometry. The output can be redirected as a .h file and used with the Arduino sketch.

Let’s look at the defaultEye images (included in the convert/defaultEye directory):

The sclera image is 200x200 pixels, allowing the eye lots of room to pivot (the screens are 128x128 pixels). Aside from icky veins and stuff, this also determines the color of the pupil. The area in the center should match the desired iris size. In the default eye case, that’s 80 pixels across.

Since the other eye designs have no visible sclera, their sclera images are blank (but still need to be present). Dragon and noSclera are 160x160 pixels (they can pivot a little), while the goat eye is 128x128, same size as the screen, thus it’s prevented automatically from moving.

This is interesting…the iris image is “unrolled,” kind of like a map of the earth. The pupil is at the bottom, outer edge of the iris at the top. The Arduino sketch morphs this back into the round shape…this is what makes the cool scaling effect possible when reacting to light.

The optimal iris image width can be computed by multiplying the round iris diameter (in pixels) by pi — 3.14 — while the image height is the iris radius in pixels. Doesn’t have to be exact, just “ish.” The default iris is 80 pixels across, so the map image is 256x64 pixels.

Dragon and noSclera irises are 160 pixels in diameter, so their iris map images are 512x80. The goat, 128 pixels across, has a 402x64 map.

Upper and lower lids are grayscale images the same dimensions as the screen (128x128 pixels). For each frame of animation, a threshold filter is applied — the eye is rendered only where the eyelid pixel values exceed some limit (which changes to create the blinking effect). Additionally, a point sample taken slightly above the pupil makes the eyelid tracking effect possible.

There are two sets of eyelid images — one set is left/right symmetrical, the other is not.

To recreate the defaultEye.h file, you’d go the “convert” directory and enter this command (as a single line):

Download: file
python defaultEye/sclera.png defaultEye/iris.png defaultEye/lid-upper-symmetrical.png defaultEye/lid-lower-symmetrical.png defaultEye/lid-upper.png defaultEye/lid-lower.png 80 > defaultEye.h

The eye images must be specified in the order: sclera, iris, symmetrical upper lid, symmetrical lower lid, asymmetrical upper lid, asymmetrical lower lid. The iris diameter (80 pixels in this example) is specified next. Then the output is redirected to the file “defaultEye.h” (you’ll want to give your own eyes different names). Check the contents of this file after running the script…any error messages will be at the end.

The resulting file can then be moved to the “graphics” folder within the “uncannyEyes” directory.

Add an #include line for whatever name you’ve assigned it. Compile and upload to the Teensy board and see what you get!

A couple of eye designs (dragon and goat) have strange pupil shapes. In this case, one more argument can be added, the filename of a “pupil map” image that assists this code in generating certain tables. Images for these are located in the corresponding directories. For example:

Download: file
python dragonEye/sclera.png dragonEye/iris.png dragonEye/lid-upper-symmetrical.png dragonEye/lid-lower-symmetrical.png dragonEye/lid-upper.png dragonEye/lid-lower.png 160 dragonEye/pupilMap.png > dragonEye.h

There are size limits to all these images. Sclera and iris images require two bytes per pixel. Eyelid images are one byte per pixel. Additionally, a lookup table equal to the iris size (80x80 pixels in the defaultEye case) requires two bytes per element.

160x160x2 + 256x64x2 + 128x128 + 128x128 + 80x80x2 = 129,536 bytes.

The code requires a little over 50K, so the resulting total compiled sketch size is about 182K, well within the 256K flash space of the Teensy. The dragon eye, with its large iris, pushes much closer to the limit. Do a little math before investing a lot of time in a new set of images, to make sure it’ll fit in the available space.

This guide was first published on Sep 07, 2015. It was last updated on Sep 07, 2015. This page (Customizing) was last updated on Oct 19, 2018.