Last active
October 3, 2022 17:54
-
-
Save jdbcode/a46c684c9a22856f2a55ef4a996259c6 to your computer and use it in GitHub Desktop.
ee_filmstrip_gif.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"name": "ee_filmstrip_gif.ipynb", | |
"provenance": [], | |
"collapsed_sections": [], | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/jdbcode/a46c684c9a22856f2a55ef4a996259c6/ee_filmstrip_gif.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "IgbXrpiFdqH5" | |
}, | |
"source": [ | |
"This notebook makes an animated GIF image from an Earth Engine time series image collection. A filmstrip is created that is chopped into frames. The frames are annotated with the image date according to text position and style you define. The resulting frames are combined into an animated GIF file using the image magick utility. The final step optionally compresses the GIF." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "dFq7l4K3GiYb" | |
}, | |
"source": [ | |
"Set up Earth Engine" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "-sh6uAlUqokF" | |
}, | |
"source": [ | |
"import ee\r\n", | |
"ee.Authenticate()\r\n", | |
"ee.Initialize()" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "WGZIJB_PGmL_" | |
}, | |
"source": [ | |
"Develop the image collection\r\n", | |
"\r\n", | |
"Setting the date in the `visImg` function is important!\r\n", | |
"\r\n", | |
"`.set('date', img.date().format('YYYY-MM-dd', tz)))`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "tkleH9M1qntL" | |
}, | |
"source": [ | |
"region = ee.Geometry.Polygon(\r\n", | |
" [[[-121.2840625, 50.14896674956192],\r\n", | |
" [-121.2840625, 24.727187209465008],\r\n", | |
" [-71.71375, 24.727187209465008],\r\n", | |
" [-71.71375, 50.14896674956192]]], None, False)\r\n", | |
"\r\n", | |
"states = ee.FeatureCollection('TIGER/2018/States')\r\n", | |
"states_outline = (ee.Image().byte()\r\n", | |
" .paint(**{'featureCollection': states, 'color': 1, 'width': 1})\r\n", | |
" .visualize(**{'palette': 'grey'}))\r\n", | |
"\r\n", | |
"vis_params = {\r\n", | |
" 'min': -30,\r\n", | |
" 'max': 30,\r\n", | |
" 'palette': ['b2182b', 'ef8a62', 'fddbc7', 'f7f7f7',\r\n", | |
" 'd1e5f0', '67a9cf', '2166ac'][::-1]\r\n", | |
"}\r\n", | |
"\r\n", | |
"tz = 'America/New_York'\r\n", | |
"dataset = (ee.ImageCollection('NOAA/NWS/RTMA')\r\n", | |
" .filterDate(ee.Date('2021-02-03', tz), ee.Date('2021-02-18', tz))\r\n", | |
" .select('TMP'))\r\n", | |
"\r\n", | |
"def visImg(img):\r\n", | |
" return (img.resample('bicubic')\r\n", | |
" .visualize(**vis_params)\r\n", | |
" .blend(states_outline)\r\n", | |
" .set('date', img.date().format('YYYY-MM-dd', tz)))\r\n", | |
"\r\n", | |
"dataset = (ee.ImageCollection.fromImages(\r\n", | |
" dataset.toList(dataset.size()).slice(0, dataset.size(), 2))\r\n", | |
" .map(visImg))\r\n", | |
"\r\n", | |
"dates = dataset.aggregate_array('date').getInfo()" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "fbBAQi48Gy0V" | |
}, | |
"source": [ | |
"Generate a thumbnail filmstrip URL from the image collection" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "pYW-m73lqvKM" | |
}, | |
"source": [ | |
"filmstrip_params = {\r\n", | |
" 'dimensions': 475,\r\n", | |
" 'region': region,\r\n", | |
" 'crs': 'EPSG:5070'\r\n", | |
"}\r\n", | |
"\r\n", | |
"filmstrip_url = dataset.getFilmstripThumbURL(filmstrip_params)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "N-oDhptSC4jM" | |
}, | |
"source": [ | |
"Get some packages and modules for dealing with images" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "vaJ4rEDmC30t" | |
}, | |
"source": [ | |
"import urllib.request\r\n", | |
"from PIL import Image, ImageDraw, ImageFont\r\n", | |
"import glob\r\n", | |
"from IPython.display import Image as ImageGIF" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "NkGqFx9vDBJ9" | |
}, | |
"source": [ | |
"Fetch the image collection thumbnail filmstrip" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "mkj3mHcUsvuU" | |
}, | |
"source": [ | |
"filmstrip_name = 'filmstrip.png'\r\n", | |
"urllib.request.urlretrieve(filmstrip_url, filmstrip_name)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "nq_D30L6HFuo" | |
}, | |
"source": [ | |
"Get some info about the filmstrip" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "2BmLoCX3w5SZ" | |
}, | |
"source": [ | |
"filmstrip_im = Image.open(filmstrip_name)\r\n", | |
"width, height = filmstrip_im.size\r\n", | |
"crop_interval = height / len(dates)\r\n", | |
"print(width, height, crop_interval)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "u2Ons-0e2aA_" | |
}, | |
"source": [ | |
"Preview the first frame" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "9c4rq1wB2VvV" | |
}, | |
"source": [ | |
"frame_0 = filmstrip_im.crop((0, 0, width, crop_interval))\r\n", | |
"print(frame_0)\r\n", | |
"display(frame_0)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "OAHaPElT5pgM" | |
}, | |
"source": [ | |
"Get a font (Consolas)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "CMAxzPjV3-lh" | |
}, | |
"source": [ | |
"font_url = 'https://github.com/tsenart/sight/raw/master/fonts/Consolas.ttf'\r\n", | |
"font_name = 'Consolas.ttf'\r\n", | |
"urllib.request.urlretrieve(font_url, font_name)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "sEt2Uo1z2Bmf" | |
}, | |
"source": [ | |
"Figure out annotation position and size" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "912ruKnNyOg4" | |
}, | |
"source": [ | |
"right = 10\r\n", | |
"down = 225\r\n", | |
"font_size = 26\r\n", | |
"fill = '#000'\r\n", | |
"\r\n", | |
"font = ImageFont.truetype(font_name, font_size)\r\n", | |
"anno_test = filmstrip_im.crop((0, 0, width, crop_interval))\r\n", | |
"ImageDraw.Draw(anno_test).text((right, down), dates[0], fill=fill, font=font)\r\n", | |
"display(anno_test)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "UvluIrJD5--S" | |
}, | |
"source": [ | |
"Loop through the frames to crop and annotate" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "rkDV9e5O6BWa" | |
}, | |
"source": [ | |
"for frame, date in enumerate(dates):\r\n", | |
" frame_im = filmstrip_im.crop(\r\n", | |
" (0, frame*crop_interval, width, (frame+1)*crop_interval))\r\n", | |
" ImageDraw.Draw(frame_im).text((right, down), date, fill=fill, font=font)\r\n", | |
" frame_im.save(str(frame).zfill(3)+'_frame.png')" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "QFCy_qyV8YvK" | |
}, | |
"source": [ | |
"Did it work?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "KFjXhHOz7zKy" | |
}, | |
"source": [ | |
"print('Found these frames:\\n')\r\n", | |
"!ls *_frame.png\r\n", | |
"print('\\nFinal frame:\\n')\r\n", | |
"Image.open(str(frame).zfill(3)+'_frame.png')" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Qx4-uXekCoeM" | |
}, | |
"source": [ | |
"Make the GIF using ImageMagick" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "8mSOTDXDTECQ" | |
}, | |
"source": [ | |
"!apt-get update\r\n", | |
"!apt install imagemagick" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "oaTEIE9XVMe3" | |
}, | |
"source": [ | |
"import os" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "Tfy8FZZWXS9l" | |
}, | |
"source": [ | |
"gif_name = 'filmstrip_magick.gif'\r\n", | |
"delay = 10\r\n", | |
"\r\n", | |
"cmd = f'convert -loop 0 -delay {delay} *_frame.png {gif_name}'\r\n", | |
"print(cmd)\r\n", | |
"os.system(cmd)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "h4MhCZmsUNE4" | |
}, | |
"source": [ | |
"with open('filmstrip_magick.gif','rb') as f:\r\n", | |
" display(ImageGIF(data=f.read(), format='png'))" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "FAtE0nXZa3X2" | |
}, | |
"source": [ | |
"Compress the GIF" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "xFBWenEqa29n" | |
}, | |
"source": [ | |
"in_gif = 'filmstrip_magick.gif'\r\n", | |
"out_gif = 'filmstrip_magick_compress.gif'\r\n", | |
"fuzz = 5 # percent\r\n", | |
"\r\n", | |
"cmd = f'convert -fuzz {fuzz}% -layers Optimize {in_gif} {out_gif}'\r\n", | |
"print(cmd)\r\n", | |
"os.system(cmd)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "b8v3r_UPfuOl" | |
}, | |
"source": [ | |
"Preview the compressed version" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "1owj6DL3bgCZ" | |
}, | |
"source": [ | |
"with open('filmstrip_magick.gif','rb') as f:\r\n", | |
" display(ImageGIF(data=f.read(), format='png'))" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "ybSM5tcruVwo" | |
}, | |
"source": [ | |
"Download the animation" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "4NdlyxBJuYXh" | |
}, | |
"source": [ | |
"from google.colab import files\r\n", | |
"files.download(out_gif) " | |
], | |
"execution_count": null, | |
"outputs": [] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment