A quick tutorial on Traits, Traits UI and Chaco PDF Print E-mail
Written by Administrator   
Sunday, 16 August 2009 17:09

Traits is a Python package from Enthought.  Traits allows us to easily give our variable an explicit type with optional bounds checking(this helps us to catch bugs as we develop our software).  Traits supports reactive programming, which Wikipedia defines as "Reactive programming is a programming paradigm oriented around data flows and the propagation of change.".   This sounds like exactly what we wish to do when writing a scientific application.  Chaco is a visualisation package from Enthought, which produces excellent plots and integrates well with Traits.

This tutorial begins with a quick introduction to Traits and then presents and discusses a simple application.  This application will present the user with a graph and three sliders.  The sliders will set the coefficients of a polynomial.  The resulting polynomial will be displayed and updated as the sliders are moved.

 

Lets start with a simple demo of the traits package.


from enthought.traits.api import HasTraits,Int,Float,Str,Property

class Person(HasTraits):
   name = Str
   age = Int
   height = Float
   weight = Float

p = Person(name="Billy",age=18,height=2.,weight=90)
#p.height = "Raspberries"   #As height expects a float, this gives an error
p.configure_traits()

print p.name,p.age,p.height,p.weight

Here we have created a class 'Person' with the attributes name,age,height,weight.  These attributes have a type associated with them, and will only accept data of that type (casting and coercion rules apply), if we give an unacceptable value Traits returns a nice descriptive error.  We then create a Person instance and call its 'configure_traits' method.  Running this program we see.

 

Traits demo image 1

Traits has very kindly provided us with a GUI to configure this instance with, the layout can be customised to our needs.  If we supply bad data the UI highlights this and blocks us from continuing.

Traits demo image 2

Make some changes and click OK, and the new values are displayed in the terminal.  All this has been achieved with minimal coding effort on the part of the user, there has been no need to pack widgets and write callbacks, Traits knows how to display a float,string etc and provides the appropriate display elements for us.  Now lets try something a little more useful.  Lets add a BMI attribute to our class.


from enthought.traits.api import HasTraits,Int,Float,Str,Property
class Person(HasTraits):
   name = Str
   age = Int
   height = Float
   weight = Float

   bmi = Property(Float,depends_on=["height","weight"])

   def _get_bmi(self):
      return self.weight/self.height**2

p = Person(name="Billy",age=18,height=2.,weight=90)
p.configure_traits()

We have specified that bmi is a property of type Float, which depends on two things height and weight.  Whenever we change one of these Traits will look for a method "_get_bmi" to calculate the new bmi (in fact Traits delays this call until the point when the attribute is actually needed).  Running this code we see the bmi field has been filled in, if we change either the height or weight, the new bmi is calculated.

Traits demo image 4

Traits demo image 3


Lets finish off by writing a custom view, this will enable us to

  • Display the traits we want in the order we want
  • Remove the age and name traits
  • Give the traits a slightly more descriptive name, including units.
  • Add a button to revert any changes we made to instance.

from enthought.traits.api import HasTraits,Int,Float,Str,Property
from enthought.traits.ui.api import View,Item,Label
from enthought.traits.ui.menu import OKButton, CancelButton,RevertButton

class Person(HasTraits):
   name = Str
   age = Int
   height = Float
   weight = Float

   bmi = Property(Float,depends_on=["height","weight"])

   view = View(
               Item("height",label = "Height / m"),
               Item("weight",label = "Weight / kg"),
               Item("bmi"),
               buttons=[RevertButton,OKButton,CancelButton],
              )

   def _get_bmi(self):
      return self.weight/self.height**2

p = Person(name="Billy",age=18,height=2.,weight=90)
p.configure_traits()

print p.name,p.age.p.height,p.weight

Traits demo image 5

Traits demo image 6

The view is quite simple, we set three traits and give them a label, we then add some buttons.  The revert button allows us to revert any changes we have made to the instance.  Items can be grouped together easily.  To finish up lets present the graph plotting app, I believe its quite readable.

from enthought.traits.api import HasTraits,Int,Float,Str,Property,Range,Array
from enthought.traits.ui.api import View,Item,Label
from enthought.chaco.chaco_plot_editor import ChacoPlotItem
from numpy import array,poly1d,arange,linspace

class PlotterApplication(HasTraits):
   c0 = Range(-5.,5.)
   c1 = Range(-5.,5.)
   c2 = Range(-5.,5.)

   xdata = Array
   ydata = Property(Array,depends_on=["c0","c1","c2"])

   #Set the coeffients to 0 as default
   def _c0_default(self): return 0.
   def _c1_default(self): return 0.
   def _c2_default(self): return 0.

   def _xdata_default(self): return linspace(-10,10,100)

   def _get_ydata(self):
      poly = poly1d([self.c2,self.c1,self.c0])
      return poly(self.xdata)

   traits_view = View(
                  ChacoPlotItem("xdata","ydata", y_bounds=(-10.,10.),y_auto=False,resizable=True,show_label=False,x_label="x",y_label="y",title=""),
                  Item('c0'),
                  Item('c1'),
                  Item('c2'),
                  resizable = True,
                  width=900, height=800,
                  title="Plot App"
                     )

if __name__ == "__main__":
   p = PlotterApplication()
   p.configure_traits()

We define five traits.  The first three are coefficients for the polynomial limited to the range (-5,5).  Then comes the array 'xdata' which has a default value set by '_xdata_default' .  The trait ydata is more interesting, it is defined as a being a property which depends on the coefficients, when the coefficients change the polynomial is recalculated.  The plot, an instance of 'ChacoPlotItem' knows that it depends on 'xdata' and 'ydata' and will replot itself whenever they change.  Then comes the view which displays the a plot window and the sliders.

For a little over 30 lines of code, this quite a powerful app.

Last Updated on Sunday, 16 August 2009 20:51