Latest Version: 0.9.6.1
  Dashboard > Pylons Cookbook > ... > Pylons Curve and Surface Fitting Tutorial > Adding graphical output
  Pylons Cookbook Log In | Sign Up   View a printable version of the current page.  
  Adding graphical output
Added by James R. Phillips, last edited by James R. Phillips on Jan 16, 2008  (view change)
Labels: 
(None)

There are many different plotting packages available for use with Python. This tutorial will use Matplotlib as its plotting package - so the first thing you need to do is install Matplotlib. If you use Debian or Ubuntu, just "sudo apt-get install python-matplotlib".

There are two approaches to serving up plots and graphs to a user's browser:

  • Option 1 - write images to files. If we do this, our software will make a plot and write binary image data to a file. The user's browser then requests this image from the web server, which reads the file and sends binary data to the browser. While this seems simple enough from a programmatic point of view, over time the server disk drive fills up unless you clean up old files - and you must provide unique file names for each session, otherwise users will download each other's image files!
  • Option 2 - send binary image data directly from Pylons. In Option 1 we wrote binary data to files; here we send it to the browser directly. No intermediate files to manage and clean up, and it is faster than writing them and then reading them again. Your code never touches the server hard drive.

In this tutorial, we will use Option 2 - send binary image data directly from Pylons. There is a small up-front investment in programming time (which the tutorial does for you), but the payoff is real and is manifested in simplified server management and web site maintenance. It is also pretty cool - you can do this with any image data! To grab the binary image data from Matplotlib, we will use the Python Image Library (PIL) - so download and install PIL. If you use Debian or Ubuntu, just "sudo apt-get install python-imaging".

Now let's make a test to find out if the plotting package installation works with Pylons on your system.



Here we'll make a very simple test that only returns an image, plotting data that we will generate on the fly. Add the following near the top of fitter.py, immediately before the class declaration:

1
2
3
4
5
6
7
8
import matplotlib
matplotlib.use('Agg')
import pylab, matplotlib.axes3d
import PIL, PIL.Image, StringIO, threading

imageThreadLock = threading.Lock() # make sure methods for graphics do not overwrite each other

timesRequested = 0 # this is only to give a different graph with each refresh

Now add a new method to fitter.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# this comment forces the wiki to indent correctly, and is not needed

    def TestMatplotlibImage(self):
        global timesRequested # this is only to give a different graph with each refresh
        global imageThreadLock # prevent threads from writing over each other's graphics

        # set the response type to PNG, since we at least hope to return a PNG image here
        response.headers['Content-type'] = 'image/png'

        # make a buffer to hold our data
        buffer = StringIO.StringIO()

        # lock graphics
        imageThreadLock.acquire()

        # we don't want different threads to write on each other's canvases, so make sure we have a new one
        pylab.close()
        canvas = pylab.get_current_fig_manager().canvas

        # quick simple plot
        pylab.plot([1+timesRequested, 2+timesRequested, 3+timesRequested], [40+timesRequested, 50+timesRequested, 60+timesRequested])

        timesRequested += 1 # this is only to give a different graph with each refresh

        canvas.draw()
        imageSize = canvas.get_width_height()
        imageRgb = canvas.tostring_rgb()
        pilImage = PIL.Image.fromstring("RGB", imageSize, imageRgb)

        pilImage.save(buffer, "PNG") # <-- we will be sending the browser a "PNG file"

        # unlock graphics
        imageThreadLock.release()

        return buffer.getvalue()

Note that we set the response type to image/png, so that the browser knows what type of binary data we're sending back. Normally this would be binary data read from an image file on the hard disk drive, but here it is the binary data in our PNG buffer. We also acquire and release threadlocks around our graphics code, so the graphs do not write on top of each other.

At this point a call to http://127.0.0.1:5000/fitter/TestMatplotlibImage should return a simple straight-line plot. Reload a few times to ensure the data shown on the X and Y axes is changing with each refresh.

Now we can add some graphs to our curve and surface fitting web site.



Note that since we are making dynamic plots and graphs, the requests to plot data will arrive at the web server after requests to curve fit have been completed. The sequence goes something like this:

  1. Our user inputs their data, selects an equation, and submits the form to the web server.
  2. The web server fits the data and returns HTML to the user's brower - including links to graphs.
  3. The user's browser reads the HTML we just sent, and makes separate requests for the graphs we linked to.
  4. The web server generates the graphs, and send them to the user's browser as binary data.
  5. The user's browser renders all this for the user to view.

Steps 2 and 4 above are independent, so on the server side we must somehow save information between these steps. To do so we will use sessions, which are conveniently provided by Pylons.

Add the following code to the end of the FitAndReturnResults() method in fitter.py so that it looks like this:

1
2
3
4
5
6
7
# this comment forces the wiki to indent correctly, and is not needed

        # save to session for graphing
        session['equation'] = c.equation
        session.save()

        return render('/results.mako')

and now add the following code to the end of the FitAndReturnResults3D() method in fitter.py so that it looks like this:

1
2
3
4
5
6
7
# this comment forces the wiki to indent correctly, and is not needed

        # save to session for graphing
        session['equation'] = c.equation
        session.save()

        return render('/results3D.mako')

The first graph we'll add is a scatter plot of the fitting error to help us determine the quality of our curve or surface fits. Add the following code to fitter.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# this comment forces the wiki to indent correctly, and is not needed

    def AbsoluteErrorVsDependentData(self):
        global imageThreadLock # prevent threads from writing over each other's graphics

        # set the response type to PNG, since we at least hope to return a PNG image here
        response.headers['Content-type'] = 'image/png'

        # make a buffer to hold our data
        buffer = StringIO.StringIO()

        # lock graphics
        imageThreadLock.acquire()

        # we don't want different threads to write on each other's canvases
        pylab.close()

        # get the equation from the session
        equation = session['equation']

        # 500x400 pixels, white background
        figure = pylab.figure(figsize=(5,4), dpi=100, frameon=False)

        # http://matplotlib.sourceforge.net/matplotlib.pylab.html#-scatter
        pylab.scatter(equation.DependentDataArray, equation.AbsoluteErrorArray)

        # label the axes and give a title
        pylab.xlabel('Dependent Data')
        pylab.ylabel('Absolute Error')
        pylab.title('Absolute Error vs. Dependent Data')

        canvas = pylab.get_current_fig_manager().canvas
        canvas.draw()
        imageSize = canvas.get_width_height()
        imageRgb = canvas.tostring_rgb()
        pilImage = PIL.Image.fromstring("RGB", imageSize, imageRgb)

        pilImage.save(buffer, "PNG") # <-- we will be sending the browser a "PNG file"

        # unlock graphics
        imageThreadLock.release()

        return buffer.getvalue()

To show the graph to our users, add this to the bottom of both results.mako and results3D.mako, just before the final </PRE> tags:

1
<img src='/fitter/AbsoluteErrorVsDependentData'>

and again try http://127.0.0.1:5000/ and http://127.0.0.1:5000/fitter/index3D - you should now have scatter plots of error vs. dependent data.

Now let's add a histogram of absolute error. Add the following code to fitter.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# this comment forces the wiki to indent correctly, and is not needed

    def AbsoluteErrorHistogram(self):
        global imageThreadLock # prevent threads from writing over each other's graphics

        # set the response type to PNG, since we at least hope to return a PNG image here
        response.headers['Content-type'] = 'image/png'

        # make a buffer to hold our data
        buffer = StringIO.StringIO()

        # lock graphics
        imageThreadLock.acquire()

        # we don't want different threads to write on each other's canvases
        pylab.close()

        # get the equation from the session
        equation = session['equation']

        # 500x400 pixels, white background
        figure = pylab.figure(figsize=(5,4), dpi=100, frameon=False)

        # http://matplotlib.sourceforge.net/matplotlib.pylab.html#-hist
        pylab.hist(equation.AbsoluteErrorArray, 10)

        # label the axes and give a title
        pylab.xlabel('Absolute Error')
        pylab.ylabel('Frequency')
        pylab.title('Histogram of Absolute Error')

        canvas = pylab.get_current_fig_manager().canvas
        canvas.draw()
        imageSize = canvas.get_width_height()
        imageRgb = canvas.tostring_rgb()
        pilImage = PIL.Image.fromstring("RGB", imageSize, imageRgb)

        pilImage.save(buffer, "PNG") # <-- we will be sending the browser a "PNG file"

        # unlock graphics
        imageThreadLock.release()

        return buffer.getvalue()

To show the graph to our users, as before we add this to the bottom of both results.mako and results3D.mako, just before the final </PRE> tags:

1
<img src='/fitter/AbsoluteErrorHistogram'>

and again try http://127.0.0.1:5000/ and http://127.0.0.1:5000/fitter/index3D - you should now have histograms of absolute error.

Now for our final graphic image, a 3D scatterplot of user data. Add the following code to fitter.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# this comment forces the wiki to indent correctly, and is not needed

    def ScatterPlot3D(self):
        global imageThreadLock # prevent threads from writing over each other's graphics

        # set the response type to PNG, since we at least hope to return a PNG image here
        response.headers['Content-type'] = 'image/png'

        # make a buffer to hold our data
        buffer = StringIO.StringIO()

        # lock graphics
        imageThreadLock.acquire()

        # we don't want different threads to write on each other's canvases
        pylab.close()

        # get the equation from the session
        equation = session['equation']

        # 500x400 pixels, white background
        figure = pylab.figure(figsize=(5,4), dpi=100, frameon=False)

        # http://www.scipy.org/Cookbook/Matplotlib/mplot3D
        ax = matplotlib.axes3d.Axes3D(figure)
        ax.scatter3D(equation.IndependentDataArray[0], equation.IndependentDataArray[1], equation.DependentDataArray)

        # label the axes and give a title
        ax.set_xlabel('X data')
        ax.set_ylabel('Y data')
        ax.set_zlabel('Z data')

        canvas = pylab.get_current_fig_manager().canvas
        canvas.draw()
        imageSize = canvas.get_width_height()
        imageRgb = canvas.tostring_rgb()
        pilImage = PIL.Image.fromstring("RGB", imageSize, imageRgb)

        pilImage.save(buffer, "PNG") # <-- we will be sending the browser a "PNG file"

        # unlock graphics
        imageThreadLock.release()

        return buffer.getvalue()

To show the 3D plot to our users, we add this to the bottom of results3D.mako, just before the final </PRE> tags:

1
<img src='/fitter/ScatterPlot3D'>

Now when you try http://127.0.0.1:5000/fitter/index3D, the results should show a 3D scatterplot of the user data.

Next: --> Conclusion

Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators
Top