1

I made a little forest fire animation. My codes are at the end of the question.

The code forestfire.py contains the functions that will spread the fire to all the forest. Then forestfire_test.py import forestfire.py as ff and from there I can set the forest on fire with my mouse by clicking on the array displayed by matplotlib.

Here is some information before I ask my question :

  • No tree : forest[i,j] = 0
  • A tree : forest[i,j] = 1
  • A tree on fire : forest[i,j] = 2
  • Hashes : forest[i,j] = 3

Basically what happens is that forest is 2 dimensional array of size n by m made of numbers between 1 and 0. The function onclick sets on fire the forest and while the forest still has trees on fire the function spreadfire spread the fire.

With the function onclick I can set the forest on fire (if I click on the array the trees will become red) and with the function start I can execute the code thanks to the button Start.

Now the issue is that the first time I execute the code it does not know what ani is (NameError: global name 'ani' is not defined) – which is normal because I call the animation ani (by trying to save it) before I even call the function start. But if I try to save ani in the function start I get a blank plot – which is also normal.

To sum up I need to save the animation after the function start is called but I can’t save it at the end of the function start otherwise I will get a blank plot. Can someone tell me what should I do?

PS : I use Spyder and the IPython console And please tell me if my explanations are not clear enough.

forestfire_test.py

import forestfire as ff
import numpy as np

import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm

from matplotlib.widgets import Button, Cursor


global forest
forest = np.random.rand(100,100)


# Colormap
greens = cm.Greens(np.linspace(0,1, num=50))
greensfill = cm.Greens(np.ones(25))
red = [(1,0,0,1)]*len(greens)
gray = [(.5,.5,.5,1)]*len(greens)

colors = np.vstack((greens, greensfill, red, gray))
mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)

# Figure
fig, ax = plt.subplots(figsize=(10,5))
fig.subplots_adjust(right=1.3)
im = ax.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)


ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.tick_params(direction='out')


cursor = Cursor(ax, useblit=True, color='red', linewidth=1)
plt.show()


# Coordinates
def onclick(event):
    x, y = int(event.xdata), int(event.ydata)
    forest[y,x] = 2.
    im.set_data(forest)
    fig.canvas.draw_idle()


fig.canvas.mpl_connect('button_press_event', onclick)   


# Start button
def start(event):
    global ani
    ani = ff.forestfire(forest)

button_ax = plt.axes([0.15, 0.45, 0.2, 0.1])
button = Button(button_ax, 'Start', color='lightgrey', hovercolor='grey')
button.on_clicked(start)


# Animation
ani.save("forestfire_test.mp4", writer = 'ffmpeg', fps=5, dpi=500)

forestfire.py

from random import random

import numpy as np
from numpy.random import choice

import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm

import matplotlib.animation as animation



def hazard(p):
    r=random()
    assert p>=0 and p<=1
    return r <= p


def chg(case):
    if case > 1.:
        return 1.
    else:
        return case


def spreadfire(forest):    

    n,m=forest.shape
    c = np.copy(forest)

    L = xrange(3)

    for i in xrange(n):
        for j in xrange(m):

            if c[i,j] == 2.:

                sautX, sautY = choice([0,1,40],p=[0.4999,0.4999,0.0002]), choice([0,1,40],p=[0.4999,0.4999,0.0002])
                Y, X = xrange(max(0,i-1-sautY),min(n,i+2+sautY),sautY+1), xrange(max(0,j-1-sautX),min(m,j+2+sautX),sautX+1)    

                for y1,y2 in zip(Y,L):
                    for x1,x2 in zip(X,L):

                        if hazard(chg(c[y1,x1])):
                            forest[y1,x1] = 2.

    return forest


def forestfire(forest):

    fig, ax = plt.subplots()

    movie, hashes = [], []

    # Colormap
    greens = cm.Greens(np.linspace(0,1, num=50))
    greensfill = cm.Greens(np.ones(25))
    red = [(1,0,0,1)]*len(greens)
    gray = [(.5,.5,.5,1)]*len(greens)

    colors = np.vstack((greens, greensfill, red, gray))
    mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)

    # Initialization
    k = 0
    firefront = 5


    forest = spreadfire(forest)

    c = np.copy(forest)
    c[np.where(c==2.)] = 3.
    hashes.append(c)

    im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
    movie.append([im])

    # Fire propagation
    while np.count_nonzero(forest == 2.) != 0:
        k += 1
        print k

        if k < firefront:

            forest = spreadfire(forest)

            c = np.copy(forest)
            c[np.where(c==2.)] = 3.
            hashes.append(c)

            im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
            movie.append([im])

        else:        
            forest = spreadfire(forest)

            c = np.copy(forest)
            c[np.where(c==2.)] = 3.
            hashes.append(c)

            forest[np.where(hashes[0]==3.)] = 3.
            im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
            movie.append([im])
            hashes.remove(hashes[0])

    return animation.ArtistAnimation(fig, movie, blit=True, repeat_delay=100)
Loïc Poncin
  • 511
  • 1
  • 11
  • 30
  • 1
    You need to move the `ani.save` inside the `start` function. `plt.show` should then only be called at the end of the script. Apart from then setting the framerate to 6 as said previously this is then working fine for me, running as a script (on python 2.7.10, matplotlib 2.0.2) in the sense that after clicking the start button the animation is saved as mp4 file to the current directory. – ImportanceOfBeingErnest Jul 25 '17 at 10:50
  • @ImportanceOfBeingErnest the framerate didn't change anything but plt.show did. If you post the answer I'll validate it. – Loïc Poncin Jul 25 '17 at 14:07

1 Answers1

1

There are two issues here, as mentionned in the comment:

  1. You need to move the ani.save inside the start function. This is because ani will only be defined once the start button is pressed.
  2. plt.show should then only be called at the end of the script.

This is then working fine for me, running as a script (on python 2.7.10, matplotlib 2.0.2) in the sense that after clicking the start button, the animation is saved as mp4 file to the current directory.

Apart from that I needed to set the framerate to 6 or above as commented in a previous question.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712