Creating animated GIFs from fruit and veg

A couple of years ago, I wrote a blog post about looking through MRI scans of fruit and veg. I took some MRI scans by Alexandr Khrapichev from the University of Oxford, ran them through an image processor, and turned them into animated GIFs. They’re very pretty and mesmerising.

That post has dozens of examples, but my favourite is this view of an artichoke, which looks like some sort of portal from science fiction:

An animated image showing a yellowy-green burst of vaguely circular lines moving outward (as we move to the middle of the artichoke), and then gradually collapsing in (as we move towards the end).
⏯︎ pause

At the time, I meant to write a follow up post explaining how I created these images, but I never got round to it. I recently rediscovered the code while clearing up an old hard drive, so let’s go through it now. All of this code uses Python and the Pillow library, because those were my go-to tools for working with images when I wrote this code.

Khrapichev saved his scans as a single large image, with the individual scans arranged into rows and columns. This is the image he published:

A grid of different views of the artichoke in yellow-green, arranged in rows and columns against a black background.
Artichoke, axial view, MRI. By Alexandr Khrapichev, University of Oxford. Used under CC BY 4.0.

So we want to cut out the individual frames from this image, put them in the right order, then stitch them into an animated GIF.

First we load the image with Pillow:

from PIL import Image

im = Image.open("artichoke.jpg")

We can crop a rectangle from an image using the crop() function, which takes the four corners of the area we want to crop. For example, this code:

im_cropped = im.crop((500, 1000, 800, 700))

will extract this part of the image:

The same grid of images as previously, but mostly dimmed and with only a small area in full colour and outlined with a red rectangle (highlighting the area to be cropped).

I started by writing a function that produces the 4-tuples that are passed to the crop() function. It moves from left-to-right, top-to-bottom, finding the coordinates of the crops:

def get_crops(im, *, columns, rows):
    column_width = im.width // columns
    row_height = im.height // rows

    for r in range(rows):
        for c in range(columns):
            x = c * column_width
            y = r * row_height
            yield (x, y, x + column_width, y + row_height)

and then I get the individual images like so:

individual_scans = [
    im.crop(c)
    for c in get_crops(im, columns=7, rows=4)
]

The exact number of individual scans varied from image to image (for example, the sagittal view of a pumpkin has 6 columns and 3 rows), so parameterising the crops was quite handy.

The same grid of images as previously, but now split into individual images, separated by white borders.

Now we save the individual frames as an animated GIF, which is a one-liner in Pillow:

individual_scans[0].save(
    "artichoke.gif",
    save_all=True,
    append_images=individual_scans[1:]
)

It’s possible to adjust the speed by passing a duration parameter, but the default seemed fine. I’m sure there’s more fun to be had by making the animation speed up or slow down at certain points!

You can download all the code from this post in this file:

make_artichoke_gif.py

And if you haven’t looked already, I really recommend getting a big monitor and reading my original post. There are pumpkins, parsimmons, passion fruit, and all sorts of other mesmerising images made entirely from MRI scans.