Joining gif animations for comparisons

Joining gif animations for comparisons

Oftentimes numerical results are especially interessting when compared to a different set of results. If results are to be compared for multiple time steps animations allow for a good comparision. The following python code allows for a quick combination of different result picutres sets into one single animation within a .gif file.

Joining images

Similar to the previous python logo image example, the Pillow Library is used again for cropping and joining the images in python. Pillow is already supplied with different python distributions, such as Anaconda, or can be added without much effort. The displayed code will be applied to join two different sets of result animation images. Within the core of the code, the original images are cropped and pasted (with an offset) into a new image file:

    #load images
    initialPic1 = PIL.Image.open(files1)
    initialPic2 = PIL.Image.open(files2)

    #image containing both
    joinedImage = PIL.Image.new(mode="RGBA", size=(2*widthNew,heightNew+120), color="white")
    draw = ImageDraw.Draw(joinedImage)

    #crop image region
    region1 =  initialPic1.crop(box)
    region2 =  initialPic2.crop(box)

    #new image size
    region1 = region1.resize((widthNew,heightNew))
    region2 = region2.resize((widthNew,heightNew))

    #paste the croped regions together
    xOffset = widthNew
    yShift = 60
    xShift = 0

    #paste images
    cropBox=( xShift,yShift, xShift+widthNew,yShift+heightNew)
    joinedImage.paste(region1,cropBox)
    cropBox=( xShift+xOffset,yShift, xShift+xOffset+widthNew,yShift+heightNew)
    joinedImage.paste(region2,cropBox)

File loop

In order to create a gif animation, a loop over both file sets is required. For this, a general fold containing the initial images, an output folder and the name identifier (including wildcards) of each image series is needed:

     #directory containing the images
     directory ="animationFiles/"
     outputFolder = "output"

     #Make output dir
     newDir=os.path.join(directory, outputFolder)
     if not os.path.exists(newDir):
        os.mkdir(newDir)

     firstSeriesNames = "color_unC*"
     secondSeriesNames = "color_onlyEx*"

Based on this setup, all files images matching the identifier can be located in the folder and added to a sorted list. Finally, the combined list can be used to iterate over all files:

      #get all files in directory
      list1 = glob.glob(directory+firstSeriesNames)
      #sort them
      list1 = sorted(list1)

      #seond series
      list2 = glob.glob(directory+secondSeriesNames)
      list2 = sorted(list2)

      for files1, files2 in zip(list1,list2):
         time = time+1

         if time%5 == 0: ## control interval the is put in gif   

Here the time parameter can be used to adjust the output interval of the picture. In addition, the parameter can be used to display the total time within the animation by using PIL.ImageDraw:

     draw = ImageDraw.Draw(joinedImage)

     displayTime = time*1000.0/365.0

     timeString = f"{displayTime:.1f}"

     #Add runtime as text
     draw.text((450, 660),"Time: "+timeString +" Years",(0,0,0),font=font1)

Gif creation

A gif can be derived by simply storing all images into an array and running the save command with the specific parameters:

   #store frames for animation
   frames = []

   frames.append(joinedImage)

   frames[0].save(os.path.join(newDir,"animation.gif"), format='GIF', append_images=frames[1:], save_all=True, duration=500, loop=0)

The result of the combined script finally could look like this: animation

Full code

In addition to cropping and joining the images, additional text labels as well as hiding of specific aspects of the original image can be useful. Therefore the full code does also include additional features that cover these aspects. The full code of the descibed sequence is given as:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    Created on Wed Nov 23 15:15:30 2022
    @author: jan
    """
    import os
    import PIL
    import glob
    from PIL import ImageFont
    from PIL import ImageDraw

    #directory containing the images
    directory ="animationFiles/"
    outputFolder = "output"

    #Make output dir
    newDir=os.path.join(directory, outputFolder)
    if not os.path.exists(newDir):
      os.mkdir(newDir)

    #setSizes
    width, height, =1206 , 1221 #rect of crop
    boxX, boxY=0,0  #upper-left location of crop area
    widthNew, heightNew = int(1206/3),int(1221/3) # half size

    #setBoxes
    box = (boxX, boxY, boxX+width, boxY+height)
    boxSmall = (boxX, boxY, boxX+width/2, boxY+height/2)

    firstSeriesNames = "color_unC*"
    secondSeriesNames = "color_onlyEx*"

    #get all files in directory
    list1 = glob.glob(directory+firstSeriesNames)
    #sort them
    list1 = sorted(list1)

    #seond series
    list2 = glob.glob(directory+secondSeriesNames)
    list2 = sorted(list2)

    #white box for hiding parts of the first image
    whiteBox = PIL.Image.new(mode="RGBA", size=(100,500), color="white")
    boxOverLegend = (1106, 0, 1106+100, 0+500)

    time = -1;

    #store frames for animation
    frames = []

    #load different sized font
    font1 =  ImageFont.truetype("OpenSans-Light.ttf", 36)
    font2 =  ImageFont.truetype("OpenSans-Light.ttf", 12)
    font3 =  ImageFont.truetype("OpenSans-Light.ttf", 20)

    #font1 = ImageFont.load_default(16)
    for files1, files2 in zip(list1,list2):

        time = time+1

        if time%7 == 0: ## control intervall the is put in gif

            print("Processing: " + files1)
            #load images
            initialPic1 = PIL.Image.open(files1)
            initialPic1.paste(whiteBox,boxOverLegend)

            initialPic2 = PIL.Image.open(files2)
            initialPic2.paste(whiteBox,boxOverLegend)

            #image containing both
            joinedImage = PIL.Image.new(mode="RGBA", size=(2*widthNew,heightNew+120), color="white")
            draw = ImageDraw.Draw(joinedImage)

            #crop into region
            region1 =  initialPic1.crop(box)
            region2 =  initialPic2.crop(box)

            #new image size
            region1 = region1.resize((widthNew,heightNew))
            region2 = region2.resize((widthNew,heightNew))

            #paste the croped regions together
            xOffset = widthNew
            yShift = 60
            xShift = 0
            cropBox=( xShift,yShift, xShift+widthNew,yShift+heightNew)
            #paste images
            joinedImage.paste(region1,cropBox)

            cropBox=( xShift+xOffset,yShift, xShift+xOffset+widthNew,yShift+heightNew)
            joinedImage.paste(region2,cropBox)

            displayTime = time*1000.0/365.0

            timeString = f"{displayTime:.1f}"

            #Add runtime as text
            draw.text((250, 460),"Time: "+timeString +" Years",(0,0,0),font=font1)
            draw.text((250, 5),"Result comparison",(0,0,0),font=font1)

            #Add legend text
            draw.text((720, 30),"Label (%)",(0,0,0),font=font3)
            draw.text((775, 60),"100",(0,0,0),font=font2)
            draw.text((775, 195),"0",(0,0,0),font=font2)

            #    joinedImage.show() # if debugging
            frames.append(joinedImage)

            #save Image seperatly
            #joinedImage.save(os.path.join(newDir,filesat.split("/").pop()))

    frames[0].save(os.path.join(newDir,"animation.gif"), format='GIF', append_images=frames[1:], save_all=True, duration=500, loop=0)
    # loop 0 = infinite

Previous Post Next Post