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.
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)
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)
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:
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