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} |
---|
22 | This document provides a simple reference for the new VTK-based |
---|
23 | realtime visualiser for the ANUGA project. It covers usage examples, |
---|
24 | descriptions of the public interface and explanation of the design. |
---|
25 | The contents of this document are accurate as of \thedate. |
---|
26 | \section{Examples} |
---|
27 | \subsection{A Simple Offline Example} |
---|
28 | This is almost the simplest example possible. It reads in a SWW file |
---|
29 | and renders the stage in blue and elevation in the default grey. |
---|
30 | \begin{verbatim} |
---|
31 | #!/usr/bin/env python |
---|
32 | # Import the offline visualiser |
---|
33 | from anuga.visualiser import OfflineVisualiser |
---|
34 | |
---|
35 | # The argument to OfflineVisualiser is the path to a sww file |
---|
36 | o = OfflineVisualiser("path/to/sww/file") |
---|
37 | |
---|
38 | # Specify the height-based-quantities to render. |
---|
39 | # Remember to set dynamic=True for time-varying quantities |
---|
40 | o.render_quantity_height("elevation", dynamic=False) |
---|
41 | o.render_quantity_height("stage", dynamic=True) |
---|
42 | |
---|
43 | # Colour the stage with an RGB 3-tuple of values in [0,1]: |
---|
44 | o.colour_height_quantity('stage', (0.0, 0.0, 0.8)) |
---|
45 | |
---|
46 | # Start the visualiser (in its own thread). |
---|
47 | o.start() |
---|
48 | |
---|
49 | # Wait for the visualiser to terminate before shutting down. |
---|
50 | o.join() |
---|
51 | \end{verbatim} |
---|
52 | The basic pattern for using a visualiser consists of 4 steps: create |
---|
53 | the visualiser, configure it to display the required quantities, start |
---|
54 | the visualiser in its own thread using start() and finally wait on all |
---|
55 | the visualiser threads using join(). |
---|
56 | \subsection{A More Complicated Realtime Example} |
---|
57 | This example demonstrates the realtime visualiser and shows off |
---|
58 | additional capabilities of both visualisers. |
---|
59 | \begin{verbatim} |
---|
60 | #!/usr/bin/env python |
---|
61 | # Import the realtime visualiser |
---|
62 | from anuga.visualiser import RealtimeVisualiser |
---|
63 | from vtk import vtkCubeAxesActor2D |
---|
64 | |
---|
65 | import time |
---|
66 | from Numeric import array |
---|
67 | from anuga.shallow_water import Domain |
---|
68 | from anuga.shallow_water import Reflective_boundary |
---|
69 | from anuga.abstract_2d_finite_volumes.mesh_factory import rectangular |
---|
70 | class 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)) |
---|
80 | M = 20 |
---|
81 | points, vertices, boundary = rectangular(M, M, len1 = 1.0, len2 = 1.0) |
---|
82 | yieldstep = 0.002 |
---|
83 | finaltime = 0.8 |
---|
84 | rect = [0.0, 0.0, 1.0, 1.0] |
---|
85 | domain = Domain(points, vertices, boundary) |
---|
86 | |
---|
87 | # Turn on the visualisation. The argument to the realtime visualier |
---|
88 | # is a domain object. |
---|
89 | v = RealtimeVisualiser(domain) |
---|
90 | # Specify the height-based-quantities to render. |
---|
91 | v.render_quantity_height("elevation", dynamic=False) |
---|
92 | v.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. |
---|
96 | v.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 |
---|
99 | v.render_axes() |
---|
100 | # Increase the number of labels on the axes |
---|
101 | v.alter_axes(vtkCubeAxesActor2D.SetNumberOfLabels, (5,)) |
---|
102 | |
---|
103 | # Draw a yellow polygon at height 2 |
---|
104 | v.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). |
---|
107 | v.start() |
---|
108 | |
---|
109 | R = Reflective_boundary(domain) |
---|
110 | domain.set_boundary( {'left': R, 'right': R, 'bottom': R, 'top': R} ) |
---|
111 | domain.set_quantity('stage', Set_Stage(0.2, 0.4, 0.25, 0.75, 2.0, 0.00)) |
---|
112 | t0 = time.time() |
---|
113 | for 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. |
---|
118 | v.evolveFinished() |
---|
119 | |
---|
120 | print 'That took %.2f seconds' %(time.time()-t0) |
---|
121 | |
---|
122 | # Wait for the visualiser to be closed |
---|
123 | v.join() |
---|
124 | \end{verbatim} |
---|
125 | Ignoring the extra code to set up and evaluate a shallow water domain, |
---|
126 | the basic principle is the same. The visualiser is created (with a |
---|
127 | domain object instead of a string as a parameter), configured (note |
---|
128 | the extra features such as the axes and the yellow polygon) and |
---|
129 | started in basically the same manner as the offline visualiser. |
---|
130 | |
---|
131 | Within the evolve loop, the visualiser's update() method is called to |
---|
132 | keep the displayed render in sync with the actual state of the domain. |
---|
133 | After the evolve loop is finished, the visualiser needs to be unhooked |
---|
134 | from the evolve loop or else the script will not terminate cleanly. |
---|
135 | Finally, the visualiser thread is join()ed so it does not close as |
---|
136 | soon as the evolve loop terminates. |
---|
137 | \section{Interface Description} |
---|
138 | \subsection{Generic Interface} |
---|
139 | The functions that form the public interface for both the offline and |
---|
140 | realtime 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} |
---|
185 | The following additional methods are available for use in the offline |
---|
186 | visualiser 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} |
---|
197 | The following additional methods are available for use in the realtime |
---|
198 | visualiser 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} |
---|
214 | The Tkinter GUI is generated by the \verb|setup_gui()| function. |
---|
215 | Subclasses are expected to override this function to add their own |
---|
216 | controls (e.g., the offline visualiser adds stepping controls). Note |
---|
217 | that the all the Tk frames are assembled using the grid layout |
---|
218 | manager. Using the pack or place layout managers will probably cause |
---|
219 | the gui to hang while Tk tries to place all the components in a way |
---|
220 | that is agreeable to all the layout managers. |
---|
221 | \subsection{VTK and Thread-Safety} |
---|
222 | Implementation of the realtime visualiser using VTK and Tkinter caused |
---|
223 | the design of the visualisers to become more complicated. Tkinter's |
---|
224 | event loop needs to be run in its own thread to avoid interfering with |
---|
225 | the calculations performed during evolve. Each visualiser is therefore |
---|
226 | created and run in its own thread. |
---|
227 | |
---|
228 | The VTK library is not thread safe and requires all data structures to |
---|
229 | be in the same thread. To safely handle this, none of the |
---|
230 | configuration functions (\verb|render_quantity_height()| etc.,) create |
---|
231 | or modify VTK data structures. No actual VTK classes are created or |
---|
232 | used until the thread is running. |
---|
233 | \subsection{Realtime Visualiser Synchronisation Issues} |
---|
234 | To prevent starvation (of either the visualiser or the evolve |
---|
235 | process), the realtime visualiser is set up to explicitly synchronise |
---|
236 | so that only one of them is running at a time. This is achieved |
---|
237 | through the use of several condition variables (Event objects in |
---|
238 | Python). This simulates the following message passing sequence:\\ |
---|
239 | |
---|
240 | \begin{turn}{-90} |
---|
241 | \includegraphics[width=0.8\textwidth]{message_passing} |
---|
242 | \end{turn} |
---|
243 | |
---|
244 | In addition, pausing the visualiser is done by making the evolution |
---|
245 | thread wait on a condition variable called \verb|sync_unpaused|. It is |
---|
246 | only clear (i.e., waiting will occur) when the visualiser's pause |
---|
247 | button has been pressed. When it has been cleared, the evolve thread |
---|
248 | will stall until it is reset by pressing the resume button. |
---|
249 | \subsection{Writing New Visualisers} |
---|
250 | The visualiser base class has been designed to be agnostic of the |
---|
251 | source data. This means that the source parameter can be any python |
---|
252 | object, provided a visualiser is written that will understand it. An |
---|
253 | overridden constructor will need to call |
---|
254 | \verb|Visualiser.__init__(self, source)| (preferably as the first |
---|
255 | command) to set up several important data structures used to store the |
---|
256 | VTK pipeline. The function \verb|self.alter_tkroot()|, which behaves |
---|
257 | in much the same way as \verb|alter_axes()| documented earlier, can be |
---|
258 | used to add new events to be run once the Tk mainloop has started. The |
---|
259 | OfflineVisualiser uses this technique to start animating the data. |
---|
260 | |
---|
261 | Subclasses of Visualiser are required to fill in the following |
---|
262 | additional 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} |
---|