source: trunk/anuga_documentation/vtk_visualiser_reference/vtk_visualiser_reference.tex @ 8813

Last change on this file since 8813 was 3966, checked in by jack, 18 years ago

Added documentation about the visualiser, including design and usage examples.

File size: 12.1 KB
Line 
1\documentclass{article}
2\usepackage{graphicx}
3\usepackage{rotating}
4\title{VTK Visualiser Reference}
5\author{Jack Kelly}
6% Override the date command, so we can get the date later.
7\let\olddate\date
8\newcommand{\thedate}{\today}
9\renewcommand{\date}[1]{
10  \olddate{#1}
11  \renewcommand{\thedate}{#1}
12}
13
14\date{10 Nov 2006} % To prevent it being updated each time the
15                   % document is generated; only update this if the
16                   % material is being updated.
17\begin{document}
18\maketitle
19\tableofcontents
20\newpage
21\section{Introduction}
22This document provides a simple reference for the new VTK-based
23realtime visualiser for the ANUGA project. It covers usage examples,
24descriptions of the public interface and explanation of the design.
25The contents of this document are accurate as of \thedate.
26\section{Examples}
27\subsection{A Simple Offline Example}
28This is almost the simplest example possible. It reads in a SWW file
29and renders the stage in blue and elevation in the default grey.
30\begin{verbatim}
31#!/usr/bin/env python
32# Import the offline visualiser
33from anuga.visualiser import OfflineVisualiser
34
35# The argument to OfflineVisualiser is the path to a sww file
36o = OfflineVisualiser("path/to/sww/file")
37
38# Specify the height-based-quantities to render.
39# Remember to set dynamic=True for time-varying quantities
40o.render_quantity_height("elevation", dynamic=False)
41o.render_quantity_height("stage", dynamic=True)
42
43# Colour the stage with an RGB 3-tuple of values in [0,1]:
44o.colour_height_quantity('stage', (0.0, 0.0, 0.8))
45
46# Start the visualiser (in its own thread).
47o.start()
48
49# Wait for the visualiser to terminate before shutting down.
50o.join()
51\end{verbatim}
52The basic pattern for using a visualiser consists of 4 steps: create
53the visualiser, configure it to display the required quantities, start
54the visualiser in its own thread using start() and finally wait on all
55the visualiser threads using join().
56\subsection{A More Complicated Realtime Example}
57This example demonstrates the realtime visualiser and shows off
58additional capabilities of both visualisers.
59\begin{verbatim}
60#!/usr/bin/env python
61# Import the realtime visualiser
62from anuga.visualiser import RealtimeVisualiser
63from vtk import vtkCubeAxesActor2D
64
65import time
66from Numeric import array
67from anuga.shallow_water import Domain
68from anuga.shallow_water import Reflective_boundary
69from anuga.abstract_2d_finite_volumes.mesh_factory import rectangular
70class Set_Stage:
71    def __init__(self, x0=0.25, x1=0.75, y0=0.0, y1=1.0, h=5.0, h0=0.0):
72        self.x0 = x0
73        self.x1 = x1
74        self.y0 = y0
75        self.y1 = y1
76        self.h  = h
77        self.h0 = h0
78    def __call__(self, x, y):
79        return self.h0 + self.h*((x>self.x0)&(x<self.x1)&(y>self.y0)&(y<self.y1))
80M = 20
81points, vertices, boundary = rectangular(M, M, len1 = 1.0, len2 = 1.0)
82yieldstep = 0.002
83finaltime = 0.8
84rect = [0.0, 0.0, 1.0, 1.0]
85domain = Domain(points, vertices, boundary)
86
87# Turn on the visualisation. The argument to the realtime visualier
88# is a domain object.
89v = RealtimeVisualiser(domain)
90# Specify the height-based-quantities to render.
91v.render_quantity_height("elevation", dynamic=False)
92v.render_quantity_height("stage", dynamic=True)
93
94# Colour the stage with a function of the quantities at that point, such as the
95# stage height: 0 and 1 are the minimum and maximum values of the stage.
96v.colour_height_quantity('stage', (lambda q:q['stage'], 0, 1))
97
98# Draw some axes on the visualiser so we can see how big the wave is
99v.render_axes()
100# Increase the number of labels on the axes
101v.alter_axes(vtkCubeAxesActor2D.SetNumberOfLabels, (5,))
102
103# Draw a yellow polygon at height 2
104v.overlay_polygon([(0, 0), (0, 0.1), (0.1, 0)], 2, colour=(1.0, 1.0, 0.0))
105
106# Start the visualiser (in its own thread).
107v.start()
108
109R = Reflective_boundary(domain)
110domain.set_boundary( {'left': R, 'right': R, 'bottom': R, 'top': R} )
111domain.set_quantity('stage', Set_Stage(0.2, 0.4, 0.25, 0.75, 2.0, 0.00))
112t0 = time.time()
113for t in domain.evolve(yieldstep = yieldstep, finaltime = finaltime):
114    v.update()
115    domain.write_time()
116# Unhook the visualiser from the evolve loop.
117# It won't shutdown cleanly unless you do this.
118v.evolveFinished()
119
120print 'That took %.2f seconds' %(time.time()-t0)
121
122# Wait for the visualiser to be closed
123v.join()
124\end{verbatim}
125Ignoring the extra code to set up and evaluate a shallow water domain,
126the basic principle is the same. The visualiser is created (with a
127domain object instead of a string as a parameter), configured (note
128the extra features such as the axes and the yellow polygon) and
129started in basically the same manner as the offline visualiser.
130
131Within the evolve loop, the visualiser's update() method is called to
132keep the displayed render in sync with the actual state of the domain.
133After the evolve loop is finished, the visualiser needs to be unhooked
134from the evolve loop or else the script will not terminate cleanly.
135Finally, the visualiser thread is join()ed so it does not close as
136soon as the evolve loop terminates.
137\section{Interface Description}
138\subsection{Generic Interface}
139The functions that form the public interface for both the offline and
140realtime visualisers are as follows:
141\begin{description}
142\item[render\_axes()]
143  Draw cube axes around the rendered quantities.  These will be
144  adjusted to the bounds of the domain automatically.
145\item[alter\_axes(func, args)]
146  Apply the function \textit{func}, with arguments tuple \textit{args} to the
147  axes. The axes are an instance of vtkCubeAxesActor2D. A sample call:
148  \verb|vis.alter_axes(vtkCubeAxesActor2D.SetNumberOfPoints, (5,))|.
149  Note that the single argument must be encased in a tuple.
150\item[render\_quantity\_height(quantityName, zScale=1.0, offset=0.0, dynamic=True)]
151  Register a new quantity to be rendered. The value of the quantity at
152  its vertices is used as the height, which is multiplied by
153  \textit{zScale} and shifted by \textit{offset}. The \textit{dynamic}
154  parameter indicates that the quantity varies as time progresses.
155\item[colour\_height\_quantity(quantityName, colour=(0.5, 0.5, 0.5))]
156  Colour a quantity that has been registered with
157  \verb|render_quantity_height|. The \textit{colour} parameter can
158  take one of two forms:
159  \begin{itemize}
160  \item A 3-tuple of values in [0,1] to specify a single colour in RGB.
161  \item A 3-tuple of values where:
162    \begin{itemize}
163    \item The first element is a function that takes as its only
164      parameter a dictionary mapping quantity names to vertex values.
165      Its return value is a list of values indicating some scalar
166      quantity at each vertex. The magnitude of these scalars is used
167      to colour the quantity.
168    \item The second element is a number that sets a lower bound on
169      the quantity. This defines what magnitude is interpreted as red.
170    \item The final element is a number that sets an upper bound on
171      the quantity. This defines what magnitude is interpreted as
172      blue.
173    \end{itemize}
174    This results in a colouring that ranges from red to blue based on
175    the values returned by the function.
176  \end{itemize}
177\item[overlay\_polygon(coords, height=0.0, colour=(1.0, 0.0, 0.0))]
178  Render a level polygon in the output. \textit{coords} is a list of
179  2-tuples representing the vertices of the polygon in the x-y plane.
180  Each 2-tuple represents one (x,y) pair. \textit{height} is the value
181  used as the z-coordinate for all the points and \textit{colour} is a
182  3-tuple of values in [0,1] that represent an RGB value.
183\end{description}
184\subsection{Additional OfflineVisualiser Methods}
185The following additional methods are available for use in the offline
186visualiser only:
187\begin{description}
188\item[precache\_height\_quantities()]
189  The offline visualiser maintains a cache of data extracted from the
190  sww file as it is being read to speed up repeated accesses to the
191  same frame. Normally this cache is empty when the visualiser is
192  \verb|start()|ed, but \verb|precache_height_quantities()| will
193  populate the cache for every frame, reducing time taken to render
194  any frame. Obviously, this increases the startup time.
195\end{description}
196\subsection{Additional RealtimeVisualiser Methods}
197The following additional methods are available for use in the realtime
198visualiser only:
199\begin{description}
200\item[update()]
201  Synchronise the rendered image to the values computed in the domain.
202  This needs to be called within the \verb|evolve()| loop or else the
203  visualiser will only display the first frame.
204\item[evolveFinished()]
205  Normally, the visualiser waits on updates from the evolve loop (sent
206  via \verb|update()|) to prevent either the visualiser or the
207  computations from being starved CPU access. Once the evolve loop has
208  finished, the visualiser will still be waiting for signals.
209  \verb|evolveFinished()| will signal the visualiser to stop waiting
210  for more input because the simulation has terminated.
211\end{description}
212\section{Design}
213\subsection{GUI}
214The Tkinter GUI is generated by the \verb|setup_gui()| function.
215Subclasses are expected to override this function to add their own
216controls (e.g., the offline visualiser adds stepping controls). Note
217that the all the Tk frames are assembled using the grid layout
218manager. Using the pack or place layout managers will probably cause
219the gui to hang while Tk tries to place all the components in a way
220that is agreeable to all the layout managers.
221\subsection{VTK and Thread-Safety}
222Implementation of the realtime visualiser using VTK and Tkinter caused
223the design of the visualisers to become more complicated. Tkinter's
224event loop needs to be run in its own thread to avoid interfering with
225the calculations performed during evolve. Each visualiser is therefore
226created and run in its own thread.
227
228The VTK library is not thread safe and requires all data structures to
229be in the same thread. To safely handle this, none of the
230configuration functions (\verb|render_quantity_height()| etc.,) create
231or modify VTK data structures. No actual VTK classes are created or
232used until the thread is running.
233\subsection{Realtime Visualiser Synchronisation Issues}
234To prevent starvation (of either the visualiser or the evolve
235process), the realtime visualiser is set up to explicitly synchronise
236so that only one of them is running at a time. This is achieved
237through the use of several condition variables (Event objects in
238Python). This simulates the following message passing sequence:\\
239
240\begin{turn}{-90}
241\includegraphics[width=0.8\textwidth]{message_passing}
242\end{turn}
243
244In addition, pausing the visualiser is done by making the evolution
245thread wait on a condition variable called \verb|sync_unpaused|. It is
246only clear (i.e., waiting will occur) when the visualiser's pause
247button has been pressed. When it has been cleared, the evolve thread
248will stall until it is reset by pressing the resume button.
249\subsection{Writing New Visualisers}
250The visualiser base class has been designed to be agnostic of the
251source data. This means that the source parameter can be any python
252object, provided a visualiser is written that will understand it. An
253overridden constructor will need to call
254\verb|Visualiser.__init__(self, source)| (preferably as the first
255command) to set up several important data structures used to store the
256VTK pipeline. The function \verb|self.alter_tkroot()|, which behaves
257in much the same way as \verb|alter_axes()| documented earlier, can be
258used to add new events to be run once the Tk mainloop has started. The
259OfflineVisualiser uses this technique to start animating the data.
260
261Subclasses of Visualiser are required to fill in the following
262additional methods, at a minimum:
263\begin{description}
264\item[setup\_grid()] Create a \verb|vtkCellArray| instance that
265  represents the triangle data and its connectivity and save it as
266  self.vtk\_cells.
267\item[update\_height\_quantity(quantityName, dynamic=True)] Create a
268  vtkPolyData object and store it in self.vtk\_polyData[quantityName].
269\item[get\_3d\_bounds()] Get the minimum and maximum bounds for the x,
270  y and z directions.  Return as a list of double in the order (xmin,
271  xmax, ymin, ymax, zmin, zmax), suitable for passing to
272  vtkCubeAxesActor2D::SetRanges().
273\item[build\_quantity\_dict()] Build and return a dictionary mapping
274  quantity name $\to$ Numeric array of vertex values for that quantity.
275\end{description}
276\end{document}
Note: See TracBrowser for help on using the repository browser.