Loading JPEG-compressed pyramidal TIFF files in the browser

Quick bit of backstory. In the world of digital preservation and archiving, there’s a standard called IIIF (pronounced triple-eye-eff). It’s designed to provide media (initially images, but now other types of media) interoperability between different archival platforms. So, if you’ve got a two high resolution images, stored in two different archives, IIIF would let you load both images side by side in a viewer for comparison. That’s pretty cool! In theory!

The reality of IIIF in practice is a little more boring – it’s become shorthand for “zooming image viewer” and there seem to be relatively few interesting examples of interoperability. Because IIIF is basically only used by nonprofit archives and museums, the tooling isn’t necessarily the latest and greatest. For zooming images (the focus of this article), one option is to precompute all of your image pyramids and write them out as individual tiles on a server, with a JSON manifest. However, storing tens of thousands of tiny JPEGs on a storage platform like S3 also has its downsides in terms of cost, complexity and performance.

Alternatively, you can run a IIIF server which does tiling on-the-fly. On-the-fly tiling can be really CPU and IO intensive, so these servers often rely on healthy caches to deliver good performance. One workaround is to store your source images in a pre-tiled, single file format like a pyramidal TIFF file. It’s basically what it sounds like – a single .tif file, which internally stores your image in a bunch of different zoom levels, broken up into small (usually 256×256 pixel) tiles. These individual tiles can be JPEG compressed to minimize storage size. A tile server can very quickly profile these tiles in response to a request, because no actual image processing is required – just disk IO. In a cloud-first world, tile servers add a lot of cost and complexity, which is one disadvantage of this approach.

Our Elevator app has supported zooming images for years, for both standard high resolution images and specialized use cases like SVS and CZI files from microscopes. Initially, our backend processors did this using the many-files approach described above. We used vips to generate deepzoom pyramids, which are essentially a set of folders each with JPEG tiles (sometimes tens of thousands). These were copied to S3, and could be loaded directly from S3 by our custom Leaflet plugin. Because the images follow a predictable naming convention (zoom_level/x_y.jpeg), there’s no translation needed to find the right tiles for a given image coordinate. This had some definitely downsides though – writing all those files was disk intensive, copying them to S3 was slow and sometimes flakey (hitting rate limits for example) and deleting a pyramid was an o(n) operation – one call per image.

As a quick fix to deal with some specific rate limit issues, we first moved to a tar+index approach using an in-house tool called Tarrific (thanks James!). After using vips to make the deepzoom pyramid, we tarred the files (without gzip compression) and copied the single tar file to the server. Tarrific then produced a JSON index of the tar, with bye offsets for each image. That was stored on the server as well. We were able to update our leaflet plugin to read the index file (itself gzip encoded), then do range requests into the tar to access the files we needed, which were still just JPEGs. This solved the S3 side of the equation, giving us a single file to upload or delete. However, I didn’t love having a totally proprietary format, and it still involved a bunch of extra disk operations during encoding.

That brings us up to the present-ish day. Now, I should mention, Elevator doesn’t support IIIF. That’s mostly because I run it as a dictatorship and I don’t find IIIF that useful. But it’s also because the IIIF format imposes some costs that wouldn’t fit in well in our AWS attribution model. That said, I’ve always tried to keep IIIF in mind as something we could support in the future, if there was a good use case. To that end, I recently spent some time looking at different file formats which would be well suited for serving via a IIIF server. In pursuit of that I did a deep dive on pyramidal tiffs. As I dug in, it seemed like in principle they should be able to be served directly to the browser, using range requests, much like we’d done with tar files. In fact, there are related formats like GeoTIFF that seem to do exactly that. Those other formats (or specifically, their tooling) didn’t seem well suited to the massive scale of images we deal with though (think tens of hundreds of gigapixels).

I recently had a long train ride across Sri Lanka (#humblebrag) and decided to see if I could make a pyramidal tiff file work directly in Leaflet without any server side processing. Turns out the answer is yes, hence this post!

Beginning at the beginning, we’re still using VIPS to do our image processing, using the tiffsave command.

vips tiffsave sourceFile --tile --pyramid --compression jpeg -Q 90 --tile-width 256 --tile-height 256 --bigtiff --depth onepixel outputFile.tiff

(The onepixel flag is there to maintain compatibly with our existing leaflet plugin, which assumes that pyramids start from a 1×1 scale instead of a single tile scale.)

The trick with pyramidal tiff files is that even though they support using jpeg compression on the image tiles, they don’t actually store full jpegs internally. Each zoom level shares a single set of quantization tables, and the individual tiles don’t have a full set of JPEG headers. Fortunately, JPEG is a super flexible format when it comes to manipulation, as it’s a linear sequence of sections, each with a start and end marker. Nothing is based on specific byte offsets, which would have made this a nightmare.

Rather than writing a full tiff parser from scratch, we’re able to leverage geotiff.js to do a lot of the heavy lifting. It handles reading the tiff header and determining the number of zoom levels (image file directories or IFDs in tiff parlance). From there, we can determine the offsets for specific tiles and get the raw data for each tile. The basic code for doing that is below (without the leaflet specific bits), though you can check out (very poorly organized) git repo for the full leaflet plugin and parser.

// first, a method to fetch the TIFF image headers
var image;
const loadIndex = async function() {
    tiff = await GeoTIFF.fromUrl("PathToYourTiffFile);
    image = await tiff.getImage();

var subimages = {};

// get the subimage for a given zoom level from the overall image.
// abuse globals because we've already broken the seal on that.
const getSubimage = async function getSubimage(coords) {
    // the headers for each zoom level need to be fetched once.
    if(subimages[coords.z] == undefined) {
        subimages[coords.z] = await tiff.getImage(maxZoom - coords.z);
    const subimage = subimages[coords.z];
    return subimage;

// fetch the raw JPEG tile data. Note that this isn't a valid jpeg.
// coords is an object with z, x, and y properties. 
const fetchRawJPEGTile = async function(coords) {

    const tileSize = 256;
    const subimage = await getSubimage(coords);
    const numTilesPerRow = Math.ceil(subimage.getWidth() / subimage.getTileWidth());
    const numTilesPerCol = Math.ceil(subimage.getHeight() / subimage.getTileHeight());
    const index = (coords.y * numTilesPerRow) + coords.x;
    // do this with our own fetch instead of geotiff so that we can get parallel requests
    // we need to trick the browser into thinking we're making the request
    // against different files or it won't parallelize the requests
    const offset = subimage.fileDirectory.TileOffsets[index];
    const byteCount = subimage.fileDirectory.TileByteCounts[index];
    const response = await fetch(PathToYourTiffFile + "?random=" + Math.random(), {
        headers: {
            Range: `bytes=${offset}-${offset+byteCount}`,
    const buffer = await response.arrayBuffer();
    return buffer

Using the above code, we can use a method like this to fetch the contents of a tile.

await loadIndex();
let myTile = await fetchRawJPEGTile({x: 10, y: 10, z:10});

There’s one big catch here though – as I mentioned, the contents of a pyramidal tiff file are jpeg compressed, but they’re not actual JPEGs.

Geotiff.js has the ability to return tiles as raster (RGB) images, but that wasn’t a great option for us for a couple reasons. First, our existing Leaflet plugin counts on being able to work with <img> tags, and getting the raster data back into an <img> tag (presumably roundtripping through a <canvas>?) would have been clunky. Second, Geotiff.js uses its own internal JPEG decoder, which seems like a waste given every modern chip can do that decoding in hardware. Instead, I went down the path of trying to turn the jpeg-compressed tiles into actual jpeg files.

Through a mix of trial and error with a hex editor, reading various tiff and jpeg specifications, and the very readable libjpeg and jpegtrans source, I arrived at the code below, which gloms together just enough data to make a valid jpeg file.

const parseTileToJPEG = async function parseTileToJPEGBlob(data, coords) {
    const subimage = await getSubimage(coords);
    const uintRaw = new Uint8Array(data);
     // magic adobe header which forces the jpeg to be interpreted as RGB instead of YCbCr
    const rawAdobeHeader = hexStringToUint8Array("FFD8FFEE000E41646F626500640000000000");
    // allocate the output array
    var mergedArray = new Uint8Array(rawAdobeHeader.length + uintRaw.length + subimage.fileDirectory.JPEGTables.length -2 - 2 - 2);
    // first two bytes of the quant tables have start of image token which we have to strip, and last two bytes are image end which we gotta strip too
    mergedArray.set(subimage.fileDirectory.JPEGTables.slice(2, -2), rawAdobeHeader.length);
    mergedArray.set(uintRaw.slice(2), rawAdobeHeader.length + subimage.fileDirectory.JPEGTables.length-2 - 2);
    const url =URL.createObjectURL(new Blob([mergedArray], {type:"image/jpeg"})); 
    return url;

So, with all of those bits put together, we can arrive at the following sample invocation, which will give us back a blob URL which can be set as the SRC for an <img>. Obviously this could be refactored in a variety of ways – I’m adapting our Leaflet code to make it more readable, but running within Leaflet means we do some abusive things vis-a-vis async/await.

let coords = {x: 10, y: 10, z:10};
let myTile = await fetchRawJPEGTile(coords);
let tileImageSource = await parseTileToJPEG(myTile, coords);

Let’s review the advantages of this approach. First, we’re able to generate a single .tiff file in our image processing pipeline. On our EC2 instances with relatively slow disk IO, this is meaningfully faster than generating a deepzoom pyramid. Synchronizing it to S3 is orders of magnitude faster as well. Working with the file on S3 is now a single file operation instead of o(n). And finally, the files we’re writing will be directly compatible with a IIIF image server if we decide we need to support that in the future.

In an ideal world, I’d love to see the IIIF spec roll in support for this approach to image handling, and just drop the requirement for a server. However, IIIF servers do lots of other stuff as well – dynamically rescaling images, rotating them, etc – so that’s probably not going to happen.

Reverse Engineering a LumeCube

One of our upcoming hardware projects (more on this later) requires a very bright, controllable light source. In the past, we’ve just used bare LEDs with our own cooling, power, control, etc. Not being someone who really understands how electricity works though, it always feels like a hassle to get a reliable setup. For this project, we instead decided to just give a LumeCube a try. They’re far from cheap, but they’re very bright, and hopefully have rock solid reliability.

LumeCube has an iOS app which allows for control over bluetooth. Although it also has a USB port, that’s only used for charging. We wanted to be able to do basic brightness control from within the Python application that will run the rest of the hardware. It doesn’t appear anyone has gone to the trouble of reverse engineering the LumeCube before, so we figured we’d give it a go. There were just two challenges:

  1. We’ve never reverse engineered a Bluetooth communications protocol.
  2. We don’t really understand how Bluetooth works.

Sticking with our philosophy of obtaining minimum-viable-knowledge, we started Googling “how to reverse engineer a bluetooth device” and ended up on this Medium post by Uri Shaked. Uri pointed us towards the “nRF Connect” app, which allows you to scan for Bluetooth devices and enumerate all of their characteristics (look at me using the lingo).

Acting on the assumption that LumeCube wasn’t going to go out of their way to secure their Bluetooth connection, we assumed brightness control would be pretty straightforward. It was just a matter of tracking down the characteristic ID and the structure of the data. To do that, we began by listing all of the characteristics in nRF Connect. Then we would disconnect and launch the official app. From there, we could adjust the brightness, then flip back to nRF Connect, rescan the characteristics, and see what had changed.

The relevant characteristic popped out very quickly. The third byte of 33826a4d-486a-11e4-a545-022807469bf0 varied from 0x00 to 0x64, or 0-100. Writing new values back to that characteristic confirmed that hunch – huzzah!

Once we’d identified the characteristic, it was just a matter of implementing some controls in Python. For that we used Bleak, which offers good documentation and cross-platform support. Below is a quick example that turns brightness to 100 (0x64).

import asyncio
import logging
from bleak import BleakClient

address = "819083F8-A230-4F61-8F94-CB69FF63D340"
LIGHT_UUID = "33826a4d-486a-11e4-a545-022807469bf0"

async def run(address):
    async with BleakClient(address) as client:
        await client.write_gatt_char(LIGHT_UUID, bytearray(b"\xfc\xa1\x64\x00"), True)
loop = asyncio.get_event_loop()

2015 Alfa Romeo 4C Coupe

I’m selling my 2015 Alfa Romeo 4C Coupe in Rosso Alfa over black leather. The car has 13,262 miles and I’m asking $42,000 OBO.

I’m the original owner for this car – I waited patiently (kind of) for the non-LE cars so that I could spec it myself, and took delivery in August 2015. I’m selling because I bought a 1979 Alfa Spider in the fall, and we don’t have space to keep both “fun” cars.

The car has always been serviced at Alfa Romeo of Minneapolis (the selling dealer) and has followed the Alfa servicing guide. Last year it had its 5 year service, including the timing belt and water pump. It’s only ever needed routine servicing.

I’ve used this car as my daily driver during summer months, as well as for road trips around Minnesota. I had the car fully wrapped in paint protection film after purchasing, so that we could go on adventures without worrying about a stray stone or an unexpected gravel road. It’s a ridiculous car which can’t help but make you giggle, with its absurd turbo whooshes and exhaust growl. Surprisingly comfortable for longer distances as well, with cruise control, bluetooth, and decent seats.


  • Rosso Alfa over black leather
  • Silver 5-hole 18″/19″ wheels
  • Red calipers
  • “Standard package” (Race exhaust, Bi-Xenon, cruise control, park assist, “premium” audio)
  • Window sticker


  • Full body 3M Pro self healing paint protection film (PPF). Covers every panel, including the headlights.
  • Alfaworks UK “Helmholtz” exhaust with carbon tips (reduces highway drone). Sale includes the stock “race” exhaust as well.
  • Carbon fiber shift paddle extensions (snap on / snap off)


  • The rear tires were replaced in mid-2019 and have about 2500 miles on them. The fronts are original and have plenty of life left.
  • Car is mechanically perfect – it’s never had a problem.
  • There are a few nicks in the paint protection film, where it has sacrificed itself to a stone. They’re detailed in the photos. There’s also a spot at the rear where the film has lifted a bit – this could be trimmed back. I’ve taken the “don’t pull on a scab” approach.
  • There’s one nick in the passenger rear wheel, where I shamefully clipped a curb.

Incident history

When the PPF was originally fitted, a quarter-sized bit of clear coat lifted and had to be repaired (see thread). In 2016, I was an idiot and cracked the passenger side side-sill while putting the car on ramps. The piece was removed and repaired. In 2017, the car was rear-ended in stop-and-go traffic. Repair involved replacing the rear bumper cover and lower diffuser. After repair, the rear bumper was re-PPF’d.

Miscellaneous Other Stuff

The sale includes everything I got with the car – the car cover, charger, manuals, keys, etc. I believe I’ve also got various bits of swag that Alfa sent early owners, which I’m happy to pass on.

For more information, contact me via cmcfadden@gmail.com.


In addition to the gallery below, you can download the full resolution images.


Hacking Framework MacOS versions

We recently got bitten by a version of the bug mentioned in my last post here. That’s an issue where codesign uses sha256 hashes instead of sha1, causing crashes on launch on MacOS 10.10.5. In this case however, the framework was a third-party binary, which we couldn’t just recompile. Instead, we needed to hack the MacOS version to trick codesign. To check the version of a framework, you can use otool:

otool -l <framework> | grep -A 3 LC_VERSION_MIN_MACOSX

In this case, it was reporting 10.12. We need 10.10. The nice thing about values like this is that they tend to follow a really specific layout in the binary, since the loader needs to find it. The header for the MacOS loader is pretty readable, and tells us where to look. LC_VERSION_MIN_MACOSX starts with a value of 0x24, and has this structure: `\

struct version_min_command {

uint32_t    cmd;        /* LC_VERSION_MIN_MACOSX or
               LC_VERSION_MIN_IPHONEOS  */
uint32_t    cmdsize;    /* sizeof(struct min_version_command) */
uint32_t    version;    /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
uint32_t    sdk;        /* X.Y.Z is encoded in nibbles xxxx.yy.zz */

Pop open your hex editor and search for the first 0x24. The first “12” we find is the MacOS version, and the second is the SDK version. So we just change our 0x0C to a 0x0A and save it. Then we can run the otool command again to confirm the versions. Now, codesign will apply both sha1 and sha256 hashes when we build. Hoorah!

Virtual MISLS

AISOS has been supporting Professor Kat Hayes on the production of “Virtual MISLS”, an iOS app that allows users to explore a historic building at Fort Snelling (Minnesota). It is designed for Google Cardboard and other mobile virtual reality headsets.

During World War II, the Military Intelligence Language School (MISLS) at Fort Snelling trained soldiers in the Japanese language to aid the war effort. Many of the buildings used for the MISLS are still standing, but are not open to the public. Because physical preservation and renovation are costly and take years to accomplish, researchers at the University of Minnesota have used a technique called photogrammetry to create a three-dimensional virtual reality (VR) model of one of the classroom buildings (Building 103) from Fort Snelling’s Upper Post. This app lets you experience the space of Building 103, where you can navigate through a guided virtual museum exhibit that uses text panels, photos, and audio clips of oral history interviews with Japanese American MISLS veterans.

Virtual reality is a powerful tool for public memory projects as it creates an immersive and interactive experience that allows users to imagine what it might have been like to be a soldier and student at the language school. This current app is a work in progress that seeks to provide alternative modes of access into spaces and histories that are under-represented at Historic Fort Snelling.

To use the app, you’ll need some sort of Google Cardboard viewer. We like the DSCVR viewer, but any viewer will work fine.  If you have a “plus” size iPhone, make sure your viewer supports that size.

The app begins with some tutorial information.  If you have further questions, please get in touch.

The Architectural Landscape of the Achaemenid Empire: Mapping the Photographs of Dr. Matthew P. Canepa

By Johnathan W. Hardy, DCL Graduate Assistant

Screenshot of story map with the tomb of Cyrus in the background


This mapping project was designed first and foremost to showcase the vast collections of the Digital Content Library. The content held on the DCL relating to the ancient Iranian world ranges from original site plans, photographs from the age of excavation in the early 20th century, to contemporary documentation and artefacts. The DCL holds over 300 individual works relating to the Achaemenid empire, with an additional 500 + works documenting the Seleucid, Arsacid, and Sasanian empires. In order to showcase this invaluable collection, each section begins with a link to a search on the DCL for pertinent images relating to the site. To better illustrate the content that the viewer is engaging with, specific photographs from Dr. Canepa’s collection are highlighted in the map with links to the full work on the DCL.

The achaemenid Kaba ye Zarduxsht at naqsh eh Rustam

A view of the Ka’ba-ye Zardosht from a break in the rock face of Naqsh-e Rustam. In the distance is the Sasanian (224-650 CE) city of Staxr.

The vision of this mapping project was to provide the interested public and students information that filled the gap between a Wikipedia article and a scholarly journal. The information provided is not overly complicated or full of specialist vocabulary but exhibits a filtered look into the latest information on the debates in Achaemenid art and architecture. Each site is given a full, linked bibliography which gives the user the ability to delve deeper into the literature if they wish to know more. I wanted to have a seamless user experience that was able to guide an inexperienced user through the material, while allowing a broad degree of flexibility in how they interact with the maps. Each tab within the map series can stand on its own, without the user having to follow a set chronological or spatial timeline.

screenshot of a story map of Persepolis

The total project consists of 42 different maps all integrated into the final product. Linking such disparate material into a single, useable product was made incredibly easy through the use of Esri’s story map options, and all maps were designed and implemented through ArcGIS Online.

I am of course thankful to LATIS and the Digital Content Library for allowing me to work on this special project, as well as Dr. Canepa for allowing his incredible photographs to be digitized.

Automated Slide Capture Rig

Chances are pretty good that if you crack open a dusty storage closest at any large University, you’ll find an old slide projector, or an unlabeled box of 35mm slides.  For decades, University lectures and research work relied heavily on slides, created from photographs taken on reversal film or charts and diagrams transferred to slides for presentations.

Since 2006, the Digital Content Library within the College of Liberal Arts has been scanning slides collected by our faculty as part of their research and instruction.  Hundreds of thousands of slides have been scanned and cataloged, creating a unique collection of material from around the world and across the decades.  Each of these slides was scanned one at a time, on a scanner like the Nikon CoolScan.  These produce a quality image, but are slow and require an operator to load each slide.  With hundreds of thousands more slides in the “someday” pile, one-at-a-time meant a lot of slides would probably never get scanned.

Inspired by some commercial products and a hacker spirit, we decided to build our own automated slide capture unit, using a classic Kodak Ektagraphic carousel projector, combined with a Canon 5D Mark III digital camera.

The basic theory of operation is to leverage the automated loading and advancing capibilities of the projector, and not much else.  The lens is removed, and the illumination bulb is replaced with a lower output (and much cooler) LED.  The camera, with a macro lens, focuses on the slide sitting inside the projector. A small Arduino controller manages the whole apparatus, advancing the projector and triggering the camera.

Because this was a hobby project, and not a core part of our jobs, it evolved very gradually over the fall of 2017.   We started by placing a large LED light panel behind the projector, with the lamp drawer removed, to get a sense of the image quality.  Some basic comparisons against our Nikon scanners hinted that the quality was as good or better as the dedicated scanners.

Because we don’t run a full-time slide scanning facility, we wanted to keep our scanning station relatively portable.  For that reason, it was important that the projector stay self contained, rather than relying on an external light source. For longevity and consistency, we wanted an LED.  We knew we needed very even illumination, which meant we would likely be using diffusion.  That meant starting with a high output light source.  If you’re looking for a high output LED, you’ve got a couple options.  You can get a fancy branded LED with a quality driver, or you can get a slighty sketchy LED directly from China, via Ebay or Alibaba.  We went with the latter.  As a bonus, it came with a basic LED driver and heatsink.

With the LED in hand and firmly mounted to its heatsink, we started figuring out how to mount it in the projector.  We decided to mount the LED in the same place as the original bulb, utilizing the existing removable light tray.  This means the light mechanism can be moved to another projector easily.  The heatsink was ever-so-slightly larger than the original lamp, so a few pieces of the mechanism had to be cut away.  For testing, the heatsink was mounted with some zip ties.

The LED runs on DC power.  Rather than trying to pack a transformer into the projector (which is exclusively AC), we simply ran the power cables out through a slot in the housing.

With the LED in place, we did some more testing to see about achieving even lighting.  Our friends in the TV studios gave us some diffusion material, so we were able to stack sheets of diffusion until we could no longer perceive any hot spots on our test slides.

Once we had the LED in place, we were pretty confident the project would work out.  The remaining bits, involving some basic fabrication and automation, were things we’d tackled with past projects like our automated photogrammetry rig and our RTI domes.  We repurposed a spare Arduino Uno to control everything.  Because the projector uses a 24 volt AC signal (!!) to control the slides, we couldn’t get away with a simple transistor control. Since we were only building one of these, we decided to buy a premade Arduino Relay shield from Evil Mad Scientist.

A little bit of code and some gator clips let us confirm that everything worked as intended.  All that was left was the cleanup and assembly.  We designed some basic mounts in Tinkercad and printed them on our Makerbot Replicator Mini.  The entire electronics package was small enough to fit into the remote storage compartment on the side of the projector.  Because the door is easily removable, it’s still a snap to move the entire setup to another projector.

At this point, the rig is assembled and in production.  We use SmartShooter 3 on a computer, attached to the camera via USB, to storage the images as they’re acquired.  We use BatchCrop to crop, clean, and straighten the images.

We can image slides at the rate of roughly 3 minutes for an 80 slide carousel.   In fact, it takes far longer to load and unload the carousel than it does to perform the capture.

[youtube https://www.youtube.com/watch?v=8Z7GMDpcPGc]

Because we used a lot of repurposed parts, we don’t have a complete Bill-of-Materials cost of the rig, but it could be replicated for well under $100 (assuming you already own the camera and projector).  We could probably put another unit together in an afternoon.

We’re really excited to have done this project, and we think it’s a great representation of the LATIS Labs ethos: we saw a problem that needed solving, iterated in small steps, and ultimately put together a workable solution.  Our next build is a little more complicated and a bit more expensive – if you’ve got a few thousand dollars lying around, get in touch!



Building a Photogrammetry Turntable

One of our goals with AISOS is to make complicated imaging tasks as easy and repeatable as possible.  We want to be able to rapidly produce high quality products, and we want the process to be accessible to folks with a minimal amount of training.

One of the ways we’ve done that for photogrammetric imaging is by building an automated turntable capture setup.  Conceptually, this is a pretty straightforward solution.  A small turntable rotates an object a fixed number of degrees, then triggers a camera to take a photo.  That process is repeated until the object has made a full 360 degree rotation.  Then the camera can be adjusted to a different angle, and the process can be repeated.

As much as we like doing cool hardware hacking, we also don’t want to suffer from “not built here” syndrome.  We investigated a variety of options for off-the-shelf solutions in this space.  There are a handful of very high end products, which also handle all the camera movement.  These are amazing, but they’re both expensive (nearly $100,000) and, more crucially, massive.  None of those solutions would physically work in our space.

There are also some smaller standalone turntable options that we explored.  However, they’re all essentially small volume homemade products, and rely on proprietary software.  We were concerned about being stuck with an expensive (still thousands of dollars) product of questionable quality.

We then began to look at building our own.  We’re not the first ones to have this idea, and fortunately there are a variety of great build plans out there.  Our favorite was the Spin project from MIT.  Spin is an automated turntable setup designed for photogrammetry with an iPhone. We knew we’d need to modify the setup to work with our camera, but the fundamentals of Spin are excellent.

For our turntable, we completely replicated the physical design of the Spin, using their laser cutter templates and their 3d printed gear.  We used the same stepper motor as well, in order to utilize their mount.  Where we differed was in the electronics.

In this post, we’ll outline the basics of our design and share our Arduino code.  We don’t currently have a full wiring schematic (Sparkfun doesn’t have a fritzing diagram for our chosen stepper driver, and none of us know Eagle – get in touch if you want to help).

Our design is based around a Sparkfun Autodriver. The autodriver is relatively expensive for a stepper driver, but it’s really easy to work with, and is a little more resilient to being abused.  Our implementation is actually based on the “getting started with the autodriver” document published by Sparkfun.  We use an Arduino Redboard as our controller, along with a protoshield for making reliable connections.

The additions we’ve made to the basic Sparkfun diagram include the camera trigger control and the ability to adjust the degrees of rotation per interval.  We’re working with a Canon digital SLR, which can be triggered via a simple contact-closure trigger.  The Canon camera trigger uses three wires – one for focus, one for firing the photo, and a ground.  Connecting either of the first two to ground is all you need to do to trigger an action.  To control that from an Arduino, you just need to use a transistor attached to a digital pin on the Arduino.  Turning on the pin closes the transistor and triggers the camera.

To control the number of degrees per interval, we added a simple rotary selector.  A rotary selector is basically just a bunch of different switches – only one can be on at a time.  We use five of the analog pins on our Arduino (set to operate as digital pins) to read the value of the switch.


So far, we’ve been very happy with the build.  We’ve taken many thousands of photos with it, and it hasn’t missed a beat.  We expect that over time, the 3d-printed gear will wear down and need to be replaced.  Beyond that, we expect it to have a lengthy service life.

This is an abbreviated build blog.  We’ll endeavor to provide complete wiring diagrams for any future builds.  For now, just get in touch if you’re interested in learning more about our build.

Creating Exhibits in Omeka

By Nathan Weaver Olson, DCL Graduate Assistant


The first step in creating an Omeka exhibit is to set up an Omeka account. Currently, the best way for individuals connected with the University of Minnesota to do this is to contact DASH Domains and open an account through them. Check out the DASH website to see if this is an option for you. If you have access to a web server, you can actually download Omeka directly. Alternatively, Omeka will host your collection at Omeka.net. Omeka.net’s basic plan will give you 500 MB of free storage, although more robust storage options are available for an annual fee.


Once you have Omeka up and running, the first step in building an Omeka exhibit is to add the digital objects or “items” you wish to include in your exhibit to your Omeka account. Do this by clicking “Add an Item”, the green button at the top of the screen. Once you have a new item, you need to add the necessary metadata (i.e. data about the data), which you can do by clicking through the various tabs entitled “Dublin Core” metadata, “Item Type Metadata”, “Files”, “Tags”, and “Map”. For the “Mud-Brick Mosques of Mali” exhibit I added some twenty-three items to our account.


Once you have added all of your items to your Omeka account, you can add a new exhibit to your account by clicking on the “Exhibits” tab and then clicking “Add an Exhibit”. The look of an individual exhibit is largely controlled by the exhibit theme. Several themes come pre-loaded in Omeka, but these are just a fraction of the themes available. In fact, users can design themes of their own. In my case, I wanted to use a theme that would allow me to insert my own background image and logo. I settled on the “BigStuff” theme and then added it to our Omeka account using the DASH Domains File Manager.


When you create a new Omeka exhibit, the first page available to you is the “Edit Exhibit” page, where you select and configure the exhibit theme, design the exhibit’s main page, and add new exhibit pages to the site.  In the image below, my theme, “BigStuff”, is clearly visible in the drop down list.

Themes can also be further configured to fit your particular aesthetic and presentation interests. “Big Stuff” allows me to insert my own background image, logo, and header images. In my case, these were images that I designed in Photoshop before uploading them to Omeka.


The content of my exhibit is stored and organized using different pages, accessed through the “Edit Exhibit” link. Each new page includes title and “slug” fields, but users are then able to add one or more blocks of content layouts entitled “File with Text”, “Gallery”, “Text”, “File”, “Geolocation Map”, “Neatline”, and “Neatline Time”.

With the exception of the “Text” layout, all of these page options allow you to pull the “Items” you created into the exhibit. When you add an item, you can also add a caption below the image. I chose to make every caption a link back to the original image in DCL Elevator. My principal goal was to showcase images from John Archer’s collection, but I was also able to locate individual mosques in space as well using Omeka’s “Geolocation Map” layout option.


Finally, when you are on the “Edit Exhibit” page, it is easy to rearrange page order and even nest some pages below others. In my case, I have five principle pages: “Mosque Design Elements”, “Regions and Styles”, “Image Gallery”, Further Reading”, and “About the Photographer”.  Yet several of these pages contain sub-pages, and sub-sub-pages, as a tool for organizing content.

Those are the basics of exhibit building in Omeka. Now that you know the basics, it’s time to get an Omeka account of your own, decide on a narrative you would like to represent as an online exhibit, and get to work adding items to your account.

The exhibit used as an example in this tutorial, “Mali’s Mud-Brick Mosques”, is just one of a number of Omeka exhibits that we have been working to create at the DCL in recent months. We should be rolling them out on our website soon. But you can find Omeka-powered exhibits and websites all over the Internet. Check out Omeka.org’s exhibit showcase for more ideas.

John Archer and Mali’s Mud-Brick Mosques: Exhibit Building with Elevator and Omeka

By Nathan Weaver Olson, DCL Graduate Assistant

Picture a vast interior space, dark and cool, its edges hidden by a forest of columns. The only visible light is that which trickles in through an ornate window screen. You are inside one of Mali’s monumental mosques, a sacred space, and the walls, columns, and even the lofty minaret towers are likely not stone but molded earth, mud bricks plastered with a layer of mud and rice hulls. It is a vulnerable structure in a region of intense heat and seasonal torrential rains, forever dependent upon an army of skilled workers to maintain its elegant and massive form. If cared for, it will last a century or more. If neglected, it will quickly fall to ruin.

In the mid 1990s, Minnesota Professor John Archer, now Professor Emeritus, visited the country of Mali with his camera and an eye for timing and image composition. He took hundreds of images of Mali’s people as well as its natural and built environments. The majority of this wonderful collection is currently housed at LATIS’ Digital Content Lab, where we are slowly adding Archer’s images to the Digital Content Library, which currently contains over 300,000 objects. So far, we have added nearly three hundred and fifty images from John Archer’s trip to Mali to the DCL and organized them into thirty-seven different “works”, each with between one and thirty-nine attached images or “views”. Among the works already available through the DCL is a collection of images of mud-brick mosques ranging from the country’s oldest, to its most iconic and monumental, to more humble examples. It is a unique collection, and now, thanks to Omeka, we have been able to create an online exhibit using many of these images to teach our users about vernacular architectural traditions in Mali while also introducing them to our object database, called DCL-Elevator.

Searching the DCL

One of our goals at the DCL is to make our collections widely available to scholars and students, not only those at the University of Minnesota, but also those working outside of the U. While many items in our collection require the user to possess an X500 to receive access, quite a few of the objects in the DCL are part of our “Open Collections”, objects available to anyone who visits the DCL’s website. Users can currently view Archer’s Mali collection on the DCL by performing an advanced search in Elevator, our database tool, and sorting by collection and keyword.

Elevator includes comprehensive and relatively intuitive finding aids, but here at the DCL we are also looking at additional tools to better familiarize users with the site and its extensive contents. Lately we have begun to do this by building exhibits using Omeka.

Omeka Exhibits

Omeka is an open-source web-publishing platform that is oriented towards users from disciplines within the Humanities. Students, professors, librarians, and archivists can all use Omeka to develop and display scholarly collections. In the case of the DCL, Omeka allows us create exhibits that highlight our collections by focusing the user’s attention on a limited number of objects from the DCL and then sending them into Elevator to find the materials themselves. In practical terms, this has meant festooning our exhibits with links that transport the user to specific images within the DCL Elevator collection. In the exhibit featured in this post, “Mali’s Mud-Brick Mosques”, I put a “Find it in the DCL” link under nearly every image I added to exhibit, as well as a link to an exhibit that Ginny Larson created for the photographer, John Archer.

The structure of an online exhibit, which is essentially a narrative, presents the user with a familiar set of tools for viewing the collection and making sense of its contents. Instead of searching through thousands of images, an online exhibit introduces the user to a finite collection that allows them to approach the more generous holdings of the DCL Elevator database from the vantage point of a specific theme. Because our Omeka exhibits serve to not only showcase the DCL Elevator Collection, but to also extend its pedagogical value for our users, I added a “Further Reading” page to the exhibit as well.

These are all things that anyone reading this post, and especially anyone connected with the University of Minnesota, can do as well. While there are free versions of Omeka available, they have a very limited online storage capacity. But U of M users are able to acquire a more substantial Omeka account through Dash Domains. The DCL has its own domain account through DASH, and through it we have the capacity to access a number of content management applications, including Omeka. To learn more about how to create your own Omeka exhibit, click here for detailed instructions.

“Mali’s Mud-Brick Mosques” is just one of a number of Omeka exhibits that we have been working to create at the DCL in recent months. We should be rolling them out soon. But in the meantime, check out the DCL’s collection and let us know if there are other themes that you would like us to explore as online exhibits.