viernes, 24 de mayo de 2013

Red Sensorial

Para esta tarea se encargo realizar una red sensorial que fuera aplicado en un área tridimensional. En mi caso busqué aplicarlo simulando un terreno natural invisible, en la cuál se tienen los nodos distribuidos en diferentes posiciones, siendo estos árboles, piedras, pozos, entre otros.

Lo que busqué aplicar es detectar a unos perros de la pradera. Explicándolo un poco tenemos un área con sensores en la que se busca saber donde se encuentra un perro de la pradera (puede ser cualquier objeto, más decidi ponerlo como ejemplo), si encontramos un perro de la pradera estando estático, que este dentro del rango de los sensores, se hace una notificación al igual que a sus sensores vecinos, Se tiene otro caso en la cual teniendo un punto central, que es la madriguera, podemos decir que pueden salir más perros de la pradera y estos se dirigen a una posición, en este otro caso, se detecta que hay un perro de la pradera y se le avisa a sus vecinos.

Herramientas:
Las herramientas usadas para la visualización fue mplot3d, que es una herramienta que ayuda a a visualización de figuras en 3d.

Código:
Para desarrollar el código se buscó realizarlo en clases en la cuál separamos nuestros sensores, nuestros intrusos, los enlaces que se tienen de éstos y la red sensorial que hace uso de los elementos ya mencionados. La idea del uso de clases es para definir objetos que éstos tienen sus atributos y ser más fácil su representación en canvas.

En el código se puede ver unas pequeñas explicaciones al igual que código comentado, donde son test de nodos estáticos que se emplearon como pruebas.

Lo importante a resaltar es el uso de arreglos donde vamos metiendo los nodos que tenemos, los nodos con las conexiones vecinos y los nodos intrusos. Haciendo esto más fácil su su detección ya que un nodo tiene como atributo su posición, así que lo que se hace es comparar en los ejes del sensor "x" "y" "z" y estando en el rango lo marca como intruso, para después guardarse en un lista.

Nodo
class Nodos(object):
def __init__(self):
self.nodo = None
self.id = 0
self.vida = 5
self.rango = 5
self.x = 0
self.y = 0
self.z = 0
def crea_nodo(self):
self.x = self.generarRandom()
self.y = self.generarRandom()
self.z = random.choice(CHOOSE_Z)
def crea_nodo_test(self, v_x, v_y, v_z):
self.x = v_x
self.y = v_y
self.z = v_z
def generarRandom(self, num=-50, num2=50):
return random.randrange(num, num2)
view raw gistfile1.py hosted with ❤ by GitHub


Nodo intruso
class Nodo_Intruso(object):
def __init__(self):
self.id = 0
self.x = 0
self.y = 0
self.x = 0
self.arr_trayectoria = []
def crea_nodo_intruso(self):
self.x = self.generarRandom()
self.y = self.generarRandom()
self.z = random.choice(CHOOSE_Z)
def crea_nodo_intruso_test(self):
self.x = 4#self.generarRandom()
self.y = 8#self.generarRandom()
self.z = 0
def generarRandom(self, num=-50, num2=50):
return random.randrange(num, num2)
view raw gistfile1.py hosted with ❤ by GitHub


Arista
class Arista_Nodos(object):
def __init__(self):
self.id = 0
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
self.z1 = 0
self.z2 = 0
def agrega_arista(self):
self.x = [self.x1, self.x2]
self.y = [self.y1, self.y2]
self.z = [self.z1, self.z2]
view raw gistfile1.py hosted with ❤ by GitHub


Red sensorial
class Red_Sensora(object):
def __init__(self, figura, ax):
self.figura = figura
self.ax = ax
self.arr_nodos = []
self.arr_aristas = []
#Intrusos
self.arr_intrusos = []
self.arr_intrusos_detectados = []
self.arr_intrusos_trayectoria = []
# vecinos de los nodos
self.arr_vecinos_nodos = []
#Mover nodos
'''
self.numpoints = None
self.stream = None
self.numpoints = None
self.stream = None
self.angle = None
self.fig = None
#self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = None
#Intrusos
def mover_intrusos(self):
self.numpoints = numpoints
self.stream = self.data_stream()
self.angle = 0
self.fig = plt.figure()
self.fig.canvas.mpl_connect('draw_event',self.forceUpdate)
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = animation.FuncAnimation(self.fig, self.update, interval=100,
init_func=self.setup_plot, frames=1)
def change_angle(self):
self.angle = (self.angle + 1)%360
def forceUpdate(self, event):
self.scat.changed()
def setup_plot(self):
global AZUL
X = next(self.stream)
self.scat = self.ax.scatter(X[:,0], X[:,1], X[:,2] , c=AZUL, s=50)
self.ax.set_xlim3d(FLOOR, CEILING)
self.ax.set_ylim3d(FLOOR, CEILING)
self.ax.set_zlim3d(FLOOR, CEILING)
return self.scat,
def data_stream(self):
data = np.zeros(( self.numpoints , 3 ))
print data
xyz = data[:,:3]
while True:
xyz += 3 * (np.random.random(( self.numpoints,3)) - 0.5)
yield data
def update(self, i):
data = next(self.stream)
print data, str("....")
self.scat._offsets3d = ( np.ma.ravel(data[:,0]) , np.ma.ravel(data[:,1]) , np.ma.ravel(data[:,2]) )
return self.scat,
#def show(self):
# plt.show()
'''
def Gen_RandLine(self, length, dims=2):
"""
Create a line using a random walk algorithm
length is the number of points for the line.
dims is the number of dimensions the line has.
"""
lineData = np.empty((dims, length))
lineData[:, 0] = np.random.rand(dims)
#lineData[:, 0] = np.array(1, 2, 2)
#print lineData
#print lineData[:, 0]
lineData[2, 0] = 0
for index in range(1, length) :
# scaling the random numbers by 0.1 so
# movement is small compared to position.
# subtraction by 0.5 is to change the range to [-0.5, 0.5]
# to allow a line to move backwards.
lineData[0, index] = lineData[0, index-1] + random.randrange(-3,3)#2#0.01
lineData[1, index] = lineData[1, index-1] + random.randrange(-3,3)#2#0.01
lineData[2, index] = lineData[2, index-1] + 0#Dejamos que el objeto se mueva en el eje de las z que es plano, suponiendo que es una superficie pla a
nodo = Nodo_Intruso()
nodo.x = lineData[0, index]
nodo.x = lineData[1, index]
nodo.x = lineData[2, index]
self.detectar_intrusos(nodo)
return lineData
def update_lines(self, num, dataLines, lines):
for line, data in zip(lines, dataLines):
# NOTE: there is no .set_data() for 3 dim data...
line.set_data(data[0:2, :num])
line.set_3d_properties(data[2,:num])
return lines
def mueve_viento(self):
#fig = plt.figure()
#ax = p3.Axes3D(fig)
# Fifty lines of random 3-D lines
data = [self.Gen_RandLine(25, 3) for index in range(5)]
#ax.plot(dat[0, 0:1], dat[1, 0:1], dat[2, 0:1])[0] for dat in data
lines = [self.ax.plot(dat[0, 0:1], dat[1, 0:1], dat[2, 0:1])[0] for dat in data]
# Setting the axes properties
'''ax.set_xlim3d([0.0, 1.0])
ax.set_xlabel('X')
ax.set_ylim3d([0.0, 1.0])
ax.set_ylabel('Y')
ax.set_zlim3d([0.0, 1.0])
ax.set_zlabel('Z')
'''
#ax.set_title('3D Test')
# Creating the Animation object
line_ani = animation.FuncAnimation(self.figura, self.update_lines, 25, fargs=(data, lines),
interval=150, blit=False)
plt.show()
'''def detectar_intrusos(self, nodo_intruso):
for nodo in self.arr_nodos:
dif_x = abs(nodo.x - nodo_intruso.x)
dif_y = abs(nodo.y - nodo_intruso.y)
dif_z = abs(nodo.z - nodo_intruso.y)
if dif_x<RADIO and dif_y<RADIO:# and dif_z<RADIO:
print "INTRUSO!"
self.arr_intrusos_detectados.append([nodo_intruso, nodo]) # se guarda el nodo intruso y el nodo que lo detecto
self.alertar_vecinos()
'''
def crear_red_nodos(self):
global n_nodos
"""nodo = Nodos()
nodo.id = 0
nodo.crea_nodo_test(0,3,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 1
nodo.crea_nodo_test(28,0,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 2
nodo.crea_nodo_test(6,16,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 3
nodo.crea_nodo_test(15,30,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 4
nodo.crea_nodo_test(15,40,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 5
nodo.crea_nodo_test(25,46,0)
self.arr_nodos.append(nodo)
"""
for i in range(n_nodos):
nodo = Nodos()
nodo.id = i
nodo.crea_nodo()
self.arr_nodos.append(nodo)
def dibuja_nodos(self):
global ROJO, CIRCULO
for nodo in self.arr_nodos:
self.ax.scatter(nodo.x, nodo.y, nodo.z, c=ROJO, marker=CIRCULO, s=20)
self.ax.text(nodo.x, nodo.y, nodo.z, nodo.id, None)
self.ax.set_xlabel('X Label')
self.ax.set_ylabel('Y Label')
self.ax.set_zlabel('Z Label')
def crea_intruso(self):
global AZUL, TRIANGULO
intruso = Nodo_Intruso()
self.id = 55
intruso.crea_nodo_intruso()
#intruso.crea_nodo_intruso_test()
return intruso
def dibuja_intruso(self, intruso):
self.ax.scatter(intruso.x, intruso.y, intruso.z, c=AZUL, marker=TRIANGULO, s=20)
self.ax.text(intruso.x, intruso.y, intruso.z, intruso.id, None)
def dibuja_uniones(self):
print "Entra"
global RADIO
id_arista = 0
for nodo in self.arr_nodos:
for nodo2 in self.arr_nodos:
if nodo != nodo2:
dif_x = abs(nodo.x - nodo2.x)
dif_y = abs(nodo.y - nodo2.y)
dif_z = abs(nodo.z - nodo2.z)
if dif_x<RADIO and dif_y<RADIO and dif_z < RADIO:# or dif_z<RADIO:
# existe relacion
arista = Arista_Nodos()
arista.id = id_arista #Agregamos el id a la arista, para la pos en el arreglo
arista.x1 = nodo.x
arista.x2 = nodo2.x
arista.y1 = nodo.y
arista.y2 = nodo2.y
arista.z2 = nodo2.z
arista.z2 = nodo2.z
arista.agrega_arista()
# lista de coneciones entre nodos
self.arr_vecinos_nodos.append([nodo, nodo2])
self.ax.plot(xs=arista.x, ys=arista.y, zs=arista.z, zdir='z', label='ys=0, zdir=z')
# dibujamos la lineas que unen a los dos nodos
self.arr_aristas.append(arista)
id_arista +=1
def detectar_intrusos(self, nodo_intruso):
for nodo in self.arr_nodos:
dif_x = abs(nodo.x - nodo_intruso.x)
dif_y = abs(nodo.y - nodo_intruso.y)
dif_z = abs(nodo.z - nodo_intruso.y)
if dif_x<RADIO and dif_y<RADIO:# and dif_z<RADIO:
#print "INTRUSO!"
self.arr_intrusos_detectados.append([nodo_intruso, nodo]) # se guarda el nodo intruso y el nodo que lo detecto
print "El nodo ", str(nodo.id), "detecto un intruso"
self.alertar_vecinos()
def alertar_vecinos(self):
'''
for nodos_intrusos, nodo_detector in self.arr_intrusos_detectados:
# recorremos la lista de nodos con los uniones
for nodo in arr_nodos:
if nodo_detector.id == nodo.id: # encontramos al soplon
'''
for nodo_intruso, nodo_detector in self.arr_intrusos_detectados:
for n1, n2 in self.arr_vecinos_nodos:
if nodo_detector == n1:
print "notificar al nodo", n2.id, "soy el soplon", n1.id
view raw gistfile1.py hosted with ❤ by GitHub


Código completo
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
import matplotlib.pyplot as plt
import matplotlib.animation as animation
n_nodos = 13
# colores nodos
ROJO = 'r'
VERDE = 'g'
AZUL = 'b'
AMARILLO = 'y'
# figura del nodo
TRIANGULO = '^'
CIRCULO = 'o'
# coordenadas
RADIO = 25
nodos = 5
#POS
FLOOR = -10
CEILING = 10
CHOOSE_Z = [-2, 0, 2]#[-0.02, 0, 0.02]
class Nodos(object):
def __init__(self):
self.nodo = None
self.id = 0
self.vida = 5
self.rango = 5
self.x = 0
self.y = 0
self.z = 0
def crea_nodo(self):
self.x = self.generarRandom()
self.y = self.generarRandom()
self.z = random.choice(CHOOSE_Z)
def crea_nodo_test(self, v_x, v_y, v_z):
self.x = v_x
self.y = v_y
self.z = v_z
def generarRandom(self, num=-50, num2=50):
return random.randrange(num, num2)
class Arista_Nodos(object):
def __init__(self):
self.id = 0
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
self.z1 = 0
self.z2 = 0
def agrega_arista(self):
self.x = [self.x1, self.x2]
self.y = [self.y1, self.y2]
self.z = [self.z1, self.z2]
class Nodo_Intruso(object):
def __init__(self):
self.id = 0
self.x = 0
self.y = 0
self.x = 0
self.arr_trayectoria = []
def crea_nodo_intruso(self):
self.x = self.generarRandom()
self.y = self.generarRandom()
self.z = random.choice(CHOOSE_Z)
def crea_nodo_intruso_test(self):
self.x = 4#self.generarRandom()
self.y = 8#self.generarRandom()
self.z = 0
def generarRandom(self, num=-50, num2=50):
return random.randrange(num, num2)
class Red_Sensora(object):
def __init__(self, figura, ax):
self.figura = figura
self.ax = ax
self.arr_nodos = []
self.arr_aristas = []
#Intrusos
self.arr_intrusos = []
self.arr_intrusos_detectados = []
self.arr_intrusos_trayectoria = []
# vecinos de los nodos
self.arr_vecinos_nodos = []
#Mover nodos
'''
self.numpoints = None
self.stream = None
self.numpoints = None
self.stream = None
self.angle = None
self.fig = None
#self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = None
#Intrusos
def mover_intrusos(self):
self.numpoints = numpoints
self.stream = self.data_stream()
self.angle = 0
self.fig = plt.figure()
self.fig.canvas.mpl_connect('draw_event',self.forceUpdate)
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = animation.FuncAnimation(self.fig, self.update, interval=100,
init_func=self.setup_plot, frames=1)
def change_angle(self):
self.angle = (self.angle + 1)%360
def forceUpdate(self, event):
self.scat.changed()
def setup_plot(self):
global AZUL
X = next(self.stream)
self.scat = self.ax.scatter(X[:,0], X[:,1], X[:,2] , c=AZUL, s=50)
self.ax.set_xlim3d(FLOOR, CEILING)
self.ax.set_ylim3d(FLOOR, CEILING)
self.ax.set_zlim3d(FLOOR, CEILING)
return self.scat,
def data_stream(self):
data = np.zeros(( self.numpoints , 3 ))
print data
xyz = data[:,:3]
while True:
xyz += 3 * (np.random.random(( self.numpoints,3)) - 0.5)
yield data
def update(self, i):
data = next(self.stream)
print data, str("....")
self.scat._offsets3d = ( np.ma.ravel(data[:,0]) , np.ma.ravel(data[:,1]) , np.ma.ravel(data[:,2]) )
return self.scat,
#def show(self):
# plt.show()
'''
def Gen_RandLine(self, length, dims=2):
"""
Create a line using a random walk algorithm
length is the number of points for the line.
dims is the number of dimensions the line has.
"""
lineData = np.empty((dims, length))
lineData[:, 0] = np.random.rand(dims)
#lineData[:, 0] = np.array(1, 2, 2)
#print lineData
#print lineData[:, 0]
lineData[2, 0] = 0
for index in range(1, length) :
# scaling the random numbers by 0.1 so
# movement is small compared to position.
# subtraction by 0.5 is to change the range to [-0.5, 0.5]
# to allow a line to move backwards.
lineData[0, index] = lineData[0, index-1] + random.randrange(-3,3)#2#0.01
lineData[1, index] = lineData[1, index-1] + random.randrange(-3,3)#2#0.01
lineData[2, index] = lineData[2, index-1] + 0#Dejamos que el objeto se mueva en el eje de las z que es plano, suponiendo que es una superficie pla a
nodo = Nodo_Intruso()
nodo.x = lineData[0, index]
nodo.x = lineData[1, index]
nodo.x = lineData[2, index]
self.detectar_intrusos(nodo)
return lineData
def update_lines(self, num, dataLines, lines):
for line, data in zip(lines, dataLines):
# NOTE: there is no .set_data() for 3 dim data...
line.set_data(data[0:2, :num])
line.set_3d_properties(data[2,:num])
return lines
def mueve_viento(self):
#fig = plt.figure()
#ax = p3.Axes3D(fig)
# Fifty lines of random 3-D lines
data = [self.Gen_RandLine(25, 3) for index in range(5)]
#ax.plot(dat[0, 0:1], dat[1, 0:1], dat[2, 0:1])[0] for dat in data
lines = [self.ax.plot(dat[0, 0:1], dat[1, 0:1], dat[2, 0:1])[0] for dat in data]
# Setting the axes properties
'''ax.set_xlim3d([0.0, 1.0])
ax.set_xlabel('X')
ax.set_ylim3d([0.0, 1.0])
ax.set_ylabel('Y')
ax.set_zlim3d([0.0, 1.0])
ax.set_zlabel('Z')
'''
#ax.set_title('3D Test')
# Creating the Animation object
line_ani = animation.FuncAnimation(self.figura, self.update_lines, 25, fargs=(data, lines),
interval=150, blit=False)
plt.show()
'''def detectar_intrusos(self, nodo_intruso):
for nodo in self.arr_nodos:
dif_x = abs(nodo.x - nodo_intruso.x)
dif_y = abs(nodo.y - nodo_intruso.y)
dif_z = abs(nodo.z - nodo_intruso.y)
if dif_x<RADIO and dif_y<RADIO:# and dif_z<RADIO:
print "INTRUSO!"
self.arr_intrusos_detectados.append([nodo_intruso, nodo]) # se guarda el nodo intruso y el nodo que lo detecto
self.alertar_vecinos()
'''
def crear_red_nodos(self):
global n_nodos
"""nodo = Nodos()
nodo.id = 0
nodo.crea_nodo_test(0,3,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 1
nodo.crea_nodo_test(28,0,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 2
nodo.crea_nodo_test(6,16,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 3
nodo.crea_nodo_test(15,30,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 4
nodo.crea_nodo_test(15,40,0)
self.arr_nodos.append(nodo)
nodo = Nodos()
nodo.id = 5
nodo.crea_nodo_test(25,46,0)
self.arr_nodos.append(nodo)
"""
for i in range(n_nodos):
nodo = Nodos()
nodo.id = i
nodo.crea_nodo()
self.arr_nodos.append(nodo)
def dibuja_nodos(self):
global ROJO, CIRCULO
for nodo in self.arr_nodos:
self.ax.scatter(nodo.x, nodo.y, nodo.z, c=ROJO, marker=CIRCULO, s=20)
self.ax.text(nodo.x, nodo.y, nodo.z, nodo.id, None)
self.ax.set_xlabel('X Label')
self.ax.set_ylabel('Y Label')
self.ax.set_zlabel('Z Label')
def crea_intruso(self):
global AZUL, TRIANGULO
intruso = Nodo_Intruso()
self.id = 55
intruso.crea_nodo_intruso()
#intruso.crea_nodo_intruso_test()
return intruso
def dibuja_intruso(self, intruso):
self.ax.scatter(intruso.x, intruso.y, intruso.z, c=AZUL, marker=TRIANGULO, s=20)
self.ax.text(intruso.x, intruso.y, intruso.z, intruso.id, None)
def dibuja_uniones(self):
print "Entra"
global RADIO
id_arista = 0
for nodo in self.arr_nodos:
for nodo2 in self.arr_nodos:
if nodo != nodo2:
dif_x = abs(nodo.x - nodo2.x)
dif_y = abs(nodo.y - nodo2.y)
dif_z = abs(nodo.z - nodo2.z)
if dif_x<RADIO and dif_y<RADIO and dif_z < RADIO:# or dif_z<RADIO:
# existe relacion
arista = Arista_Nodos()
arista.id = id_arista #Agregamos el id a la arista, para la pos en el arreglo
arista.x1 = nodo.x
arista.x2 = nodo2.x
arista.y1 = nodo.y
arista.y2 = nodo2.y
arista.z2 = nodo2.z
arista.z2 = nodo2.z
arista.agrega_arista()
# lista de coneciones entre nodos
self.arr_vecinos_nodos.append([nodo, nodo2])
self.ax.plot(xs=arista.x, ys=arista.y, zs=arista.z, zdir='z', label='ys=0, zdir=z')
# dibujamos la lineas que unen a los dos nodos
self.arr_aristas.append(arista)
id_arista +=1
def detectar_intrusos(self, nodo_intruso):
for nodo in self.arr_nodos:
dif_x = abs(nodo.x - nodo_intruso.x)
dif_y = abs(nodo.y - nodo_intruso.y)
dif_z = abs(nodo.z - nodo_intruso.y)
if dif_x<RADIO and dif_y<RADIO:# and dif_z<RADIO:
#print "INTRUSO!"
self.arr_intrusos_detectados.append([nodo_intruso, nodo]) # se guarda el nodo intruso y el nodo que lo detecto
print "El nodo ", str(nodo.id), "detecto un intruso"
self.alertar_vecinos()
def alertar_vecinos(self):
'''
for nodos_intrusos, nodo_detector in self.arr_intrusos_detectados:
# recorremos la lista de nodos con los uniones
for nodo in arr_nodos:
if nodo_detector.id == nodo.id: # encontramos al soplon
'''
for nodo_intruso, nodo_detector in self.arr_intrusos_detectados:
for n1, n2 in self.arr_vecinos_nodos:
if nodo_detector == n1:
print "notificar al nodo", n2.id, "soy el soplon", n1.id
def main():
n_intrusos = 5
arr_bad = []
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
red = Red_Sensora(fig, ax)
red.crear_red_nodos()
red.dibuja_nodos()
red.dibuja_uniones()
for intruso in range(n_intrusos):
intruso = red.crea_intruso()
red.dibuja_intruso(intruso)
arr_bad.append(intruso)
for intruso in arr_bad:
red.detectar_intrusos(intruso)
red.mueve_viento()
plt.show() #ploteamos
main()
view raw gistfile1.py hosted with ❤ by GitHub


Notas:
El código se desarrollo en conjunto con un compañero de clase, en la cuál nos basamos en la construcción de la red sensorial, para ya después aplicarla en diferentes casos.

Resultados:
Explicando un poco de los resultados, distinguimos varios elementos los cuáles son:
  • Nodo rojo: Sensor
  • Triángulo azul: Intruso en estado estático
  • Aristas: Conexiones entre los sensores
  • Lineas en movimiento: Intruso en movimiento

En los resultados podemos ver como se detecta al intruso y es notificado a sus vecinos:




enlance: http://www.youtube.com/watch?v=sEWnaijWq_U

Referencias:
http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html
http://nbviewer.ipython.org/urls/raw.github.com/jrjohansson/scientific-python-lectures/master/Lecture-4-Matplotlib.ipynb
http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/
http://pybonacci.wordpress.com/2012/12/16/creando-una-animacion-con-matplotlib-y-ffmpeg/
https://astrowiki.physics.ox.ac.uk/pub/EaHS12/PythonPage/Matplotlib.pdf


1 comentario: