Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jdbcode/3856edd182917d608f57ca384ed13749 to your computer and use it in GitHub Desktop.
Save jdbcode/3856edd182917d608f57ca384ed13749 to your computer and use it in GitHub Desktop.
ee_filmstrip_gif_precipitable_water_2023_01_03.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"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/3856edd182917d608f57ca384ed13749/ee_filmstrip_gif_precipitable_water_2023_01_03.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\n",
"ee.Authenticate()\n",
"ee.Initialize()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Install imagemagick"
],
"metadata": {
"id": "lPqh0lJUjWDM"
}
},
{
"cell_type": "code",
"metadata": {
"id": "8mSOTDXDTECQ"
},
"source": [
"!apt install imagemagick"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "N-oDhptSC4jM"
},
"source": [
"Get some packages and modules for dealing with files and images"
]
},
{
"cell_type": "code",
"metadata": {
"id": "vaJ4rEDmC30t"
},
"source": [
"import glob\n",
"import os\n",
"import urllib.request\n",
"from IPython.display import Image as ImageGIF\n",
"from PIL import Image, ImageDraw, ImageFont"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "WGZIJB_PGmL_"
},
"source": [
"Develop the image collection\n",
"\n",
"Note that setting the 'date' property in the `visImg` function is important for annotating the frames."
]
},
{
"cell_type": "code",
"metadata": {
"id": "tkleH9M1qntL"
},
"source": [
"region = ee.Geometry.Polygon(\n",
" [[[-30, 70],\n",
" [-30, -12],\n",
" [-180.0, -12],\n",
" [-180.0, 70]]], None, False)\n",
"\n",
"waterVapor = (ee.ImageCollection('NOAA/GFS0P25')\n",
" .filter(ee.Filter.date('2023-01-03', '2023-01-04'))\n",
" .limit(120)\n",
" .select('precipitable_water_entire_atmosphere'))\n",
"\n",
"landWater = ee.Image('MERIT/DEM/v1_0_3').mask();\n",
"\n",
"minWaterVaporVal = 15\n",
"\n",
"landWaterVisParams = {'min': 0, 'max': 1, 'palette': ['D3D3D3', '383838']}\n",
"\n",
"waterVaporVisParams = {\n",
" 'min': minWaterVaporVal,\n",
" 'max': 50,\n",
" 'palette': ['c6dbef', '9ecae1', '6baed6', '4292c6', '2171b5', '084594'],\n",
" 'opacity': 0.65\n",
"}\n",
"\n",
"shadowVisParams = {'min': 1, 'max': 1, 'palette': '808080', 'opacity': 0.5}\n",
"\n",
"def visImg(img):\n",
" date = ee.Date(img.get('forecast_time')).format()\n",
" img = (img.resample('bicubic'))\n",
"\n",
" mask = img.gt(minWaterVaporVal)\n",
" height = img.multiply(10000)\n",
" hillshade = ee.Terrain.hillshade(height, 310, 45).visualize()\n",
" shadow = (ee.Terrain.hillShadow(\n",
" height.updateMask(mask).unmask(0), 310, 70, 100, False).Not().selfMask())\n",
"\n",
" landWaterVis = landWater.visualize(**landWaterVisParams)\n",
" waterVaporVis = img.visualize(**waterVaporVisParams)\n",
" shadowVis = shadow.visualize(**shadowVisParams)\n",
"\n",
" return (landWaterVis.blend(\n",
" hillshade.blend(waterVaporVis).updateMask(mask))\n",
" .blend(shadowVis).set('date', date))\n",
"\n",
"visCol = waterVapor.map(visImg)\n",
"\n",
"dates = visCol.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 = {\n",
" 'dimensions': 475,\n",
" 'region': region,\n",
" 'crs': 'EPSG:32663'\n",
"}\n",
"\n",
"filmstrip_url = visCol.getFilmstripThumbURL(filmstrip_params)"
],
"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'\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)\n",
"width, height = filmstrip_im.size\n",
"crop_interval = height / len(dates)\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))\n",
"print(frame_0)\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'\n",
"font_name = 'Consolas.ttf'\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 = 235\n",
"down = 230\n",
"font_size = 22\n",
"fill = '#000'\n",
"\n",
"font = ImageFont.truetype(font_name, font_size)\n",
"anno_test = filmstrip_im.crop((0, 0, width, crop_interval))\n",
"ImageDraw.Draw(anno_test).text((right, down), dates[0], fill=fill, font=font)\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": [
"!rm *_frame.png\n",
"for frame, date in enumerate(dates):\n",
" frame_im = filmstrip_im.crop(\n",
" (0, frame*crop_interval, width, (frame+1)*crop_interval))\n",
" ImageDraw.Draw(frame_im).text((right, down), date, fill=fill, font=font)\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')\n",
"!ls *_frame.png\n",
"print('\\nFinal frame:\\n')\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": "Tfy8FZZWXS9l"
},
"source": [
"gif_name = 'filmstrip_magick.gif'\n",
"delay = 15\n",
"\n",
"cmd = f'convert -loop 0 -delay {delay} *_frame.png {gif_name}'\n",
"print(cmd)\n",
"os.system(cmd)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "h4MhCZmsUNE4"
},
"source": [
"with open('filmstrip_magick.gif','rb') as f:\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'\n",
"out_gif = 'filmstrip_magick_compress.gif'\n",
"fuzz = 5 # percent\n",
"\n",
"cmd = f'convert -fuzz {fuzz}% -layers Optimize {in_gif} {out_gif}'\n",
"print(cmd)\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:\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\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