Archive for 19 julio 2009

Yet Another Sunday Post

Sorry guys but I’ll write this post in English, you know how it is, when you feel like Englishing, just write it in English :)
It’s raining now, and it has been since yesterday’s evening. It had been a long time since it last rained, and it reminded me howit was to see the things you see all the time suddenly different.

Well enough about rain, though there isn’t really much to say. I’ve been trying to avoid doing anything university-related for this last two days, I don’t want to be more tired at the end of the holidays than I was at the beginning. It’s been kind of hard, since when you are doing trivial things like reading some forgotten blogs or playing some game or talking with someone, guilt creeps upon you, guilt that says “why aren’t you studying now? you have four exams ahead”.

That being said, I’ll lay out my current home-cultural activity (since the city has not much culture to offer, and I don’t have much will to hunt for it)

Concerning music, I’ve been liking more and more Les Cowboys Fringants (their album Break Syndical is amazing, and En Berne turned out to be a very good song) and Rilo Kiley (The Absence of God makes me feel really comfortable, totally recommended). At the same time I’ve found out that I don’t like Josh Woodward‘s songs that much, they’re not my style, although Inertia and Memorized are ok.

In the TV shows side, a bit of The Simpsons and ol’ beloved and almost forgotten Daria (lalala you’re standing on my neck… you’re standing on my neck!), I remembered how cool that show was.
In the movie front there’s been no action in the last weeks.

“What about books?” you ask (well probably you don’t, but let me think you do), well, I started reading A Tale of Two Cities a couple of days ago, and realised Mr. Dickens was funnier than I had imagined, though the vocabulary used is pretty complex.
I’m also reading about Knowledge Engineering, a very interesting subject, but from a not so interesting book, so we’ll see what happens.

 
Pesadilla de Segundo Cuatrimestre

 
And that’s pretty much all there is, I went back to reading xkcd, seeing some good photos taken by this guy and taking my own, with the low quality you can always expect from me, of course :)

Graffitti

Graffitti

 

Plaza de los naranjos

Plaza de los naranjos

QT ‘n Plot – Simple Ones

Los gráficos pueden hacer divertidas cosas que no lo son, pueden convertir números fríos que no dicen mucho en cosas que cobran sentido, pueden poner una sonrisa en tu cara y hacerte ver el mundo color de rosa. Bah, no tanto, pero están buenos. Un ejemplo es éste, cortesía de xkcd

fucking_blue_shells

Matplotlib es una librería (vulg. biblioteca) de Python basada en MATLAB, que nos permite hacer super-gráficos de muchas formas y colores, y si andamos con ganas,podemos integrarlos a nuestros programas de todos los días.

Lo que voy a hacer ahora es mostrar cómo trazar un par de gráficos (y cuando digo un par me refiero a 2) e incrustarlos en una aplicación que tiene una GUI basada en PyQt4 (léase Pie Cute). Los gráficos están inspirados en el clásico chiste de Ramón, que tenía un camión y se iba para Gijón. No cuento el chiste acá por respeto al señor Ramón.

 

 

 
Cuando terminemos, la cosa va a quedar algo así:

 
CamionDeRamon

Los requisitos mínimos para poder hacer esto son:

  • Python (yo uso la versión 2.6)
  • Numpy (tiene clases y funciones para trabajar con arreglos)
  • Matplotlib: hay un instalador para windows y una versión independiente de la plataforma (en este último caso va a haber que usar distutils, como lo indiqué en mi post anterior)
  • PyQt
  • (opcional) Un buen entorno de desarrollo, como Eclipse+PyDev, o Wing IDE Professional si andan platudos

Empecemos por el principio:

  1. Importamos las cosas que vamos a usar:
    import sys
    
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    import matplotlib
    import matplotlib.pyplot as plot
    
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    import numpy as np
    
  2. Creamos e inicializamos la ventana en la que va a transcurrir la acción:
    class FormMain(QMainWindow):
        def __init__(self, parent=None):
            super(FormMain, self).__init__(parent)
    
            self.cc = plot.matplotlib.colors.ColorConverter()
            self.__initialize__()
    
  3. Escribimos el código de inicialización de la ventana:
    def __initialize__(self):
            self.setWindowTitle(QString(u"El camión de Ramón"))
    
            # mainWidget
            mainWidget = QWidget(self)
            self.setCentralWidget(mainWidget)
    
            # layoutMain
            self.layoutMain = QVBoxLayout()
            mainWidget.setLayout(self.layoutMain)
    
            # Figure and canvas configuration
            self.fig = Figure((6.0,6.0), dpi=100,\
    facecolor=self.cc.to_rgb("lightsteelblue"))
            self.fig.subplots_adjust(hspace=0.25)
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(mainWidget)
            self.layoutMain.addWidget(self.canvas)
            self.axes = self.fig.add_subplot(211)
            self.axes.get_xaxis().set_visible(False)
            self.axes2 = self.fig.add_subplot(212)
    
            #layoutSettings
            self.layoutSettings = QHBoxLayout()
            self.layoutMain.addLayout(self.layoutSettings)
    
            #lblInitialSpeed
            self.lblInitialSpeed = QLabel("Velocidad inicial:")
            self.layoutSettings.addWidget(self.lblInitialSpeed)
    
            #spnInitialSpeed
            self.spnInitialSpeed = QSpinBox()
            self.spnInitialSpeed.setValue(6)
            self.spnInitialSpeed.setMaximum(20)
            self.layoutSettings.addWidget(self.spnInitialSpeed)
            self.connect(self.spnInitialSpeed, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addSpacing(12)
    
            #lblAcceleration
            self.lblAcceleration = QLabel(u"Aceleración:")
            self.layoutSettings.addWidget(self.lblAcceleration)
    
            #spnAcceleration
            self.spnAcceleration = QSpinBox()
            self.spnAcceleration.setValue(7)
            self.spnAcceleration.setMaximum(10)
            self.layoutSettings.addWidget(self.spnAcceleration)
            self.connect(self.spnAcceleration, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addSpacing(12)
    
            #lblTime
            self.lblTime = QLabel("Tiempo:")
            self.layoutSettings.addWidget(self.lblTime)
    
            #spnTime
            self.spnTime = QSpinBox()
            self.spnTime.setValue(20)
            self.spnTime.setMinimum(1)
            self.spnTime.setMaximum(20)
            self.layoutSettings.addWidget(self.spnTime)
            self.connect(self.spnTime, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addStretch()
    
            self.UpdatePlots()
    

    Ahora la explicación por partes:

    self.fig = Figure((6.0,6.0), dpi=100, facecolor=self.cc.to_rgb("lightsteelblue"))

    Definimos una figura de 6 pulgadas por 6 pulgadas, con una resolución de 100dpi y un fondo de color lightsteelblue.

    self.fig.subplots_adjust(hspace=0.25)
    

    Establecemos una distancia entre los dos subgráficos igual a 1/4 del tamaño de ellos.

    self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(mainWidget)
            self.layoutMain.addWidget(self.canvas)
    

    Creamos un Canvas que es el mismísimo QWidget que se va a mostrar en la ventana, y lo agregamos al layout principal.

    self.axes = self.fig.add_subplot(211)
            self.axes.get_xaxis().set_visible(False)
            self.axes2 = self.fig.add_subplot(212)
    

    Creamos los dos subgráficos. De los números que se le pasan a add_subplot, el primer dígito indica el número de filas que va a haber, y el segundo indica el número de columnas (estos dos dígitos tienen que ser iguales en ambos subgráficos, o van a pasar cosas muy desagradables, que ni el más macho de los machos soportaría ver). El tercer dígito indica en qué orden va a aparecer el subgráfico.

    Lo que viene después es código de creación de componentes visuales (Spinboxes y Labels) que se explica solo, lo único que hace falta aclarar es que conectamos las Spinboxes al método UpdatePlots, que vamos a definir a continuación, para que se actualicen los gráficos cada vez que se modifican los valores de velocidad, aceleración y tiempo. Finalmente llamamos a ese método para mostrar los gráficos por primera vez.

  4. Actualizamos los gráficos de velocidad y de posición, y reflejamos los cambios en el Canvas:

    def UpdatePlots(self):
            # Update the subplots
            self.UpdateSpeedPlot()
            self.UpdatePositionPlot()
            self.canvas.draw()
    
  5. Definimos dos pequeños métodos para obtener la velocidad y la posición dados los parámetros especificados por el usuario. Es importante aclarar que como el parámetro time va a ser un arreglo, estos métodos van a devolver arreglos.

    def Speed(self, initialSpeed, acceleration, time):
            return initialSpeed + acceleration * time
        
        def Position(self, initialPosition, initialSpeed, acceleration, time):
            return initialPosition + initialSpeed * time + 0.5 * acceleration \
    * (time ** 2)
    
  6. Cada uno de estos métodos actualiza uno de los gráficos, no explico nada porque ya está todo dicho en los comentarios :) (notarán que antes estaban en inglés y cambiaron súbitamente de idioma, pero es que lo hice para ahorrarme la explicación aparte)

    def UpdateSpeedPlot(self):
            v0 = self.spnInitialSpeed.value()
            a = self.spnAcceleration.value()
            t = self.spnTime.value()
            
            # x es un arreglo con cada segundo, desde 0 a t.
            x = np.arange(0, t + 1)
            # y es un arreglo con la velocidad en cada segundo.
            y = self.Speed(v0, a, x)    
            
            # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
            self.axes.clear()
            self.axes.set_ylabel("Velocidad (m/s)")
            self.axes.set_xlabel("Tiempo (s)")
            # Hacemos que se muestre la cuadrícula.
            self.axes.grid(True)
            
            # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
            line = plot.Line2D(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # Agregamos la línea al gráfico y la trazamos.  
            self.axes.add_line(line)
            self.axes.plot()
            
            # Las tres últimas instrucciones son equivalentes en este caso a
            # self.axes.plot(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # pero es más prolijo trabajar cada línea por separado, especialmente cuando
            # trabajamos con varias.
            
            # Fijamos los límites de los ejes coordenados.
            self.axes.set_xlim(0, t)
            self.axes.set_ylim(0, 220)
            
        def UpdatePositionPlot(self):
            v0 = self.spnInitialSpeed.value()
            a = self.spnAcceleration.value()
            t = self.spnTime.value()
            
            # x es un arreglo con cada segundo, desde 0 a t.
            x = np.arange(0, t + 1)
            # y es un arreglo con la posición en cada segundo.
            y = self.Position(0, v0, a, x)
            
            # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
            self.axes2.clear()
            self.axes2.set_ylabel(u"Posición (m)")
            self.axes2.set_xlabel("Tiempo (s)")
            # Hacemos que se muestre la cuadrícula.
            self.axes2.grid(True)
            
            # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
            line = plot.Line2D(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # Agregamos la línea al gráfico y la trazamos.   
            self.axes2.add_line(line)
            self.axes2.plot()
            
            # Fijamos los límites de los ejes coordenados.
            self.axes2.set_xlim(0, t)
            self.axes2.set_ylim(0, 2500)       
    

Bueno, ya estamos, acá dejo el código completo, diviértanse haciendo gráficos y no se lastimen :)

#coding=utf-8

import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import matplotlib
import matplotlib.pyplot as plot

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np


class FormMain(QMainWindow):
    def __init__(self, parent=None):
        super(FormMain, self).__init__(parent)
        self.cc = plot.matplotlib.colors.ColorConverter()
        self.__initialize__()
        
        
    def __initialize__(self):
        self.setWindowTitle(QString(u"El camión de Ramón"))
        
        # mainWidget
        mainWidget = QWidget(self)
        self.setCentralWidget(mainWidget)
        
        # layoutMain
        self.layoutMain = QVBoxLayout()
        mainWidget.setLayout(self.layoutMain)
        
        # Figure and canvas configuration 
        self.fig = Figure((6.0,6.0), dpi=100, facecolor=self.cc.to_rgb("lightsteelblue"))
        self.fig.subplots_adjust(hspace=0.25)
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(mainWidget)
        self.layoutMain.addWidget(self.canvas)
        self.axes = self.fig.add_subplot(211)
        self.axes.get_xaxis().set_visible(False)
        self.axes2 = self.fig.add_subplot(212)
        
        #layoutSettings
        self.layoutSettings = QHBoxLayout()
        self.layoutMain.addLayout(self.layoutSettings)
        
        #lblInitialSpeed
        self.lblInitialSpeed = QLabel("Velocidad inicial:")
        self.layoutSettings.addWidget(self.lblInitialSpeed)
        
        #spnInitialSpeed
        self.spnInitialSpeed = QSpinBox()
        self.spnInitialSpeed.setValue(6)
        self.spnInitialSpeed.setMaximum(20)
        self.layoutSettings.addWidget(self.spnInitialSpeed)
        self.connect(self.spnInitialSpeed, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addSpacing(12)
        
        #lblAcceleration
        self.lblAcceleration = QLabel(u"Aceleración:")
        self.layoutSettings.addWidget(self.lblAcceleration)
        
        #spnAcceleration
        self.spnAcceleration = QSpinBox()
        self.spnAcceleration.setValue(7)
        self.spnAcceleration.setMaximum(10)
        self.layoutSettings.addWidget(self.spnAcceleration)
        self.connect(self.spnAcceleration, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addSpacing(12)

        #lblTime
        self.lblTime = QLabel("Tiempo:")
        self.layoutSettings.addWidget(self.lblTime)
        
        #spnTime
        self.spnTime = QSpinBox()
        self.spnTime.setValue(20)
        self.spnTime.setMinimum(1)
        self.spnTime.setMaximum(20)
        self.layoutSettings.addWidget(self.spnTime)
        self.connect(self.spnTime, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addStretch()
        
        
        self.UpdatePlots()
        
    def Speed(self, initialSpeed, acceleration, time):
        return initialSpeed + acceleration * time
    
    def Position(self, initialPosition, initialSpeed, acceleration, time):
        return initialPosition + initialSpeed * time + 0.5 * acceleration * (time ** 2)
    
    def UpdatePlots(self):
        # Update the subplots
        self.UpdateSpeedPlot()
        self.UpdatePositionPlot()
        self.canvas.draw()

    def UpdateSpeedPlot(self):
        v0 = self.spnInitialSpeed.value()
        a = self.spnAcceleration.value()
        t = self.spnTime.value()
        
        # x es un arreglo con cada segundo, desde 0 a t.
        x = np.arange(0, t + 1)
        # y es un arreglo con la velocidad en cada segundo.
        y = self.Speed(v0, a, x)    
        
        # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
        self.axes.clear()
        self.axes.set_ylabel("Velocidad (m/s)")
        self.axes.set_xlabel("Tiempo (s)")
        # Hacemos que se muestre la cuadrícula.
        self.axes.grid(True)
        
        # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
        line = plot.Line2D(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # Agregamos la línea al gráfico y la trazamos.  
        self.axes.add_line(line)
        self.axes.plot()
        
        # Las tres últimas instrucciones son equivalentes en este caso a
        # self.axes.plot(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # pero es más prolijo trabajar cada línea por separado, especialmente cuando
        # trabajamos con varias.
        
        # Fijamos los límites de los ejes coordenados.
        self.axes.set_xlim(0, t)
        self.axes.set_ylim(0, 220)
        
    def UpdatePositionPlot(self):
        v0 = self.spnInitialSpeed.value()
        a = self.spnAcceleration.value()
        t = self.spnTime.value()
        
        # x es un arreglo con cada segundo, desde 0 a t.
        x = np.arange(0, t + 1)
        # y es un arreglo con la posición en cada segundo.
        y = self.Position(0, v0, a, x)
        
        # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
        self.axes2.clear()
        self.axes2.set_ylabel(u"Posición (m)")
        self.axes2.set_xlabel("Tiempo (s)")
        # Hacemos que se muestre la cuadrícula.
        self.axes2.grid(True)
        
        # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
        line = plot.Line2D(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # Agregamos la línea al gráfico y la trazamos.   
        self.axes2.add_line(line)
        self.axes2.plot()
        
        # Fijamos los límites de los ejes coordenados.
        self.axes2.set_xlim(0, t)
        self.axes2.set_ylim(0, 2500)
       

app = QApplication(sys.argv)
frmMain = FormMain()
frmMain.show()
app.exec_()