1 |
""" |
2 |
@author: John NGUI |
3 |
""" |
4 |
|
5 |
import vtk |
6 |
import tempfile, os, sys |
7 |
from constant import Source, VizType, ColorMode |
8 |
try: |
9 |
import esys.escript |
10 |
except ImportError: |
11 |
print "Warning: importing esys.escript failed." |
12 |
|
13 |
try: |
14 |
PYVISI_WORKDIR=os.environ['PYVISI_WORKDIR'] |
15 |
except KeyError: |
16 |
PYVISI_WORKDIR='.' |
17 |
try: |
18 |
PYVISI_TEST_DATA_ROOT=os.environ['PYVISI_TEST_DATA_ROOT'] |
19 |
except KeyError: |
20 |
PYVISI_TEST_DATA_ROOT='.' |
21 |
|
22 |
class DataCollector: |
23 |
""" |
24 |
Class that defines a data collector. A data collector is used to read |
25 |
data from an XML file or from an escript object directly. |
26 |
|
27 |
@attention: A DataCollector instance can only be used to specify one |
28 |
scalar, vector and tensor attribute from a source at any one time. If a |
29 |
second scalar, vector or tensor attribute needs to be specified from the |
30 |
same source, a second DataCollector instance must be created. |
31 |
|
32 |
@attention: When a series of XML files or escript objects are read |
33 |
(using 'setFileName' or 'setData' in a for-loop), the 'setActiveScalar' / |
34 |
'setActiveVector' / 'setActiveTensor' have to be called for each new file |
35 |
(provided a specific field needs to be loaded) as all active fields |
36 |
specified from the previous file goes back to the default once a new file |
37 |
is read. |
38 |
""" |
39 |
|
40 |
def __init__(self, source = Source.XML): |
41 |
""" |
42 |
Initialise the data collector. |
43 |
|
44 |
@type source: L{Source <constant.Source>} constant |
45 |
@param source: Source type |
46 |
""" |
47 |
|
48 |
self.__source = source |
49 |
self.__count = 0 # Keeps track of the number of files/sources read. |
50 |
|
51 |
if(source == Source.XML): # Source is an XML file. |
52 |
self.__vtk_xml_reader = vtk.vtkXMLUnstructuredGridReader() |
53 |
# Source is a escript data object using a temp file in the background. |
54 |
elif (self.__source == Source.ESCRIPT): |
55 |
self.__vtk_xml_reader = vtk.vtkXMLUnstructuredGridReader() |
56 |
# Create a temporary .xml file and retrieve its path. |
57 |
self.__tmp_file = tempfile.mkstemp(suffix=".xml")[1] |
58 |
|
59 |
def __del__(self): |
60 |
""" |
61 |
Perform some clean up of the temporary file. |
62 |
""" |
63 |
|
64 |
if (self.__source == Source.ESCRIPT): |
65 |
if os.access(self.__tmp_file,os.F_OK): os.unlink(self.__tmp_file) |
66 |
|
67 |
def setFileName(self, file_name): |
68 |
""" |
69 |
Set the XML file name to read. |
70 |
|
71 |
@type file_name: String |
72 |
@param file_name: Name of the file to read |
73 |
""" |
74 |
|
75 |
if(self.__source == Source.XML): |
76 |
# Check whether the specified file exists, otherwise exit. |
77 |
if not(os.access(file_name, os.F_OK)): |
78 |
raise IOError("ERROR: '%s' file does NOT exists." % file_name) |
79 |
|
80 |
self.__vtk_xml_reader.SetFileName(file_name) |
81 |
# Update must be called after SetFileName to make the reader |
82 |
# up-to-date. Otherwise, some output values may be incorrect. |
83 |
self.__vtk_xml_reader.Update() |
84 |
self.__output = self.__vtk_xml_reader.GetOutput() |
85 |
self.__get_attribute_lists() |
86 |
|
87 |
# Count has to be larger than zero because when setFileName is |
88 |
# called for the first time, the data set mapper has not yet been |
89 |
# instantiated. Therefore, the range of the mapper can only be |
90 |
# updated after the first file/source has been read. |
91 |
if(self.__count > 0): |
92 |
self._updateRange() |
93 |
|
94 |
self.__count+=1 |
95 |
|
96 |
else: |
97 |
raise ValueError("Source type %s does not support \ |
98 |
'setFileName'\n" % self.__source) |
99 |
|
100 |
def setData(self,**args): |
101 |
""" |
102 |
Create data using the <name>=<data> pairing. Assumption is made |
103 |
that the data will be given in the appropriate format. |
104 |
""" |
105 |
|
106 |
if self.__source == Source.ESCRIPT: |
107 |
esys.escript.saveVTK(self.__tmp_file,**args) |
108 |
self.__vtk_xml_reader.SetFileName(self.__tmp_file) |
109 |
# Modified must be called for setData but NOT for |
110 |
# setFileName. If Modified is not called, only the first file |
111 |
# will always be displayed. The reason Modified is used is |
112 |
# because the same temporary file name is always used |
113 |
# (previous file is overwritten). Modified MUST NOT be used in |
114 |
# setFileName, it can cause incorrect output such as map. |
115 |
self.__vtk_xml_reader.Modified() |
116 |
# Update must be called after Modified. If Update is called before |
117 |
# Modified, then the first/second image(s) may not be updated |
118 |
# correctly. |
119 |
self.__vtk_xml_reader.Update() |
120 |
self.__output = self.__vtk_xml_reader.GetOutput() |
121 |
self.__get_attribute_lists() |
122 |
|
123 |
if(self.__count > 0): |
124 |
self._updateRange() |
125 |
|
126 |
self.__count+=1 |
127 |
else: |
128 |
raise ValueError("Source type %s does not support 'setData'\n" \ |
129 |
% self.__source) |
130 |
|
131 |
def setActiveScalar(self, scalar): |
132 |
""" |
133 |
Specify the scalar field to load. |
134 |
|
135 |
@type scalar: String |
136 |
@param scalar: Scalar field to load from the file. |
137 |
""" |
138 |
|
139 |
# Check whether the specified scalar is available in either point |
140 |
# or cell data. If not available, program exits. |
141 |
|
142 |
# NOTE: This check is similar to the check used in _getScalarRange |
143 |
# but this is used only when a scalar attribute has been specified. |
144 |
if scalar in self.__point_attribute['scalars']: |
145 |
self._getOutput().GetPointData().SetActiveScalars(scalar) |
146 |
elif scalar in self.__cell_attribute['scalars']: |
147 |
self._getOutput().GetCellData().SetActiveScalars(scalar) |
148 |
else: |
149 |
raise IOError("ERROR: No scalar called '%s' is available." % scalar) |
150 |
|
151 |
def setActiveVector(self, vector): |
152 |
""" |
153 |
Specify the vector field to load. |
154 |
|
155 |
@type vector: String |
156 |
@param vector: Vector field to load from the file. |
157 |
""" |
158 |
|
159 |
# Check whether the specified vector is available in either point |
160 |
# or cell data. If not available, program exits. |
161 |
|
162 |
# NOTE: This check is similar to the check used in _getVectorRange |
163 |
# but this is used only when a vector attribute has been specified. |
164 |
if vector in self.__point_attribute['vectors']: |
165 |
self._getOutput().GetPointData().SetActiveVectors(vector) |
166 |
elif vector in self.__cell_attribute['vectors']: |
167 |
self._getOutput().GetCellData().SetActiveVectors(vector) |
168 |
else: |
169 |
raise IOError("ERROR: No vector called '%s' is available." % vector) |
170 |
|
171 |
def setActiveTensor(self, tensor): |
172 |
""" |
173 |
Specify the tensor field to load. |
174 |
|
175 |
@type tensor: String |
176 |
@param tensor: Tensor field to load from the file. |
177 |
""" |
178 |
|
179 |
# Check whether the specified tensor is available in either point |
180 |
# or cell data. If not available, program exits. |
181 |
|
182 |
# NOTE: This check is similar to the check used in _getTensorRange |
183 |
# but this is used only when a tensor attribute has been specified. |
184 |
if tensor in self.__point_attribute['tensors']: |
185 |
self._getOutput().GetPointData().SetActiveTensors(tensor) |
186 |
elif tensor in self.__cell_attribute['tensors']: |
187 |
self._getOutput().GetCellData().SetActiveTensors(tensor) |
188 |
else: |
189 |
raise IOError("ERROR: No tensor called '%s' is available." % tensor) |
190 |
|
191 |
# 'object' is set to 'None' because some types of visualization have |
192 |
# two ranges that needs to be updated while others only have one. |
193 |
def _paramForUpdatingMultipleSources(self, viz_type, color_mode, mapper, |
194 |
object = None): |
195 |
""" |
196 |
Parameters required to update the necessary data when two or more |
197 |
files or escript objects are read. |
198 |
|
199 |
@type viz_type: : L{VizType <constant.VizType>} constant |
200 |
@param viz_type: Type if visualization |
201 |
@type color_mode: L{ColorMode <constant.ColorMode>} constant |
202 |
@param color_mode: Type of color mode |
203 |
@type mapper: vtkDataSetMapper |
204 |
@param mapper: Mapped data |
205 |
@type object: vtkPolyDataAlgorithm (i.e. vtkContourFilter, vtkGlyph3D, \ |
206 |
etc) |
207 |
@param object: Polygonal data |
208 |
""" |
209 |
|
210 |
self.__viz_type = viz_type |
211 |
self.__color_mode = color_mode |
212 |
self.__mapper = mapper |
213 |
self.__object = object |
214 |
|
215 |
def _updateRange(self): |
216 |
""" |
217 |
Update the necessary range(s) when two or more files or escript objects |
218 |
are read. |
219 |
""" |
220 |
|
221 |
if self.__viz_type == VizType.MAP or \ |
222 |
self.__viz_type == VizType.ELLIPSOID or \ |
223 |
self.__viz_type == VizType.CARPET: |
224 |
self.__mapper.SetScalarRange(self._getScalarRange()) |
225 |
elif self.__viz_type == VizType.VELOCITY: |
226 |
if self.__color_mode == ColorMode.VECTOR: |
227 |
self.__object.SetRange(self._getVectorRange()) |
228 |
self.__mapper.SetScalarRange(self._getVectorRange()) |
229 |
elif self.__color_mode == ColorMode.SCALAR: |
230 |
self.__object.SetRange(self._getScalarRange()) |
231 |
self.__mapper.SetScalarRange(self._getScalarRange()) |
232 |
elif self.__viz_type == VizType.CONTOUR: |
233 |
self.__object.GenerateValues( |
234 |
self.__object.GetNumberOfContours(), |
235 |
self._getScalarRange()[0], |
236 |
self._getScalarRange()[1]) |
237 |
self.__mapper.SetScalarRange(self._getScalarRange()) |
238 |
elif self.__viz_type == VizType.STREAMLINE: |
239 |
if self.__color_mode == ColorMode.VECTOR: |
240 |
self.__mapper.SetScalarRange(self._getVectorRange()) |
241 |
elif self.__color_mode == ColorMode.SCALAR: |
242 |
self.__mapper.SetScalarRange(self._getScalarRange()) |
243 |
|
244 |
def __get_array_type(self, arr): |
245 |
""" |
246 |
Return whether an array type is scalar, vector or tensor by looking |
247 |
at the number of components in the array. |
248 |
|
249 |
@type arr: vtkDataArray |
250 |
@param arr: An array from the source. |
251 |
@rtype: String |
252 |
@return: Array type ('scalar', vector' or 'tensor') |
253 |
""" |
254 |
|
255 |
# Number of components in an array. |
256 |
num_components = arr.GetNumberOfComponents() |
257 |
|
258 |
if num_components == 1: |
259 |
return 'scalars' |
260 |
elif num_components == 3: |
261 |
return 'vectors' |
262 |
elif num_components == 9: |
263 |
return 'tensors' |
264 |
|
265 |
def __get_attribute_list(self, data): |
266 |
""" |
267 |
Return the available scalar, vector and tensor attributes |
268 |
(either point or cell data). |
269 |
|
270 |
@type data: vtkPointData or vtkCellData |
271 |
@param data: Available point data or cell data from the source |
272 |
@rtype: Dictionary |
273 |
@return: Dictionary containing the available scalar, vector and \ |
274 |
tensor attributes |
275 |
""" |
276 |
|
277 |
attribute = {'scalars':[], 'vectors':[], 'tensors':[]} |
278 |
if data: |
279 |
num_arrays = data.GetNumberOfArrays() # Number of arrays. |
280 |
for i in range(num_arrays): |
281 |
name = data.GetArrayName(i) # Get an array name. |
282 |
type = self.__get_array_type(data.GetArray(i)) # Get array type. |
283 |
attribute[type].extend([name]) # Add array name to dictionary. |
284 |
|
285 |
return attribute |
286 |
|
287 |
def __get_attribute_lists(self): |
288 |
""" |
289 |
Get all the available point and cell data attributes from the source. |
290 |
""" |
291 |
|
292 |
# Get all the available point data attributes into a list. |
293 |
self.__point_attribute = \ |
294 |
self.__get_attribute_list(self._getOutput().GetPointData()) |
295 |
# Get all the available cell data attribute into another list. |
296 |
self.__cell_attribute = \ |
297 |
self.__get_attribute_list(self._getOutput().GetCellData()) |
298 |
|
299 |
def _getScalarRange(self): |
300 |
""" |
301 |
Return the scalar range. |
302 |
|
303 |
@rtype: Two column tuple containing numbers |
304 |
@return: Scalar range |
305 |
""" |
306 |
|
307 |
# Check whether any scalar is available in either point or cell data. |
308 |
# If not available, program exits. |
309 |
|
310 |
# NOTE: This check is similar to the check used in _setActiveScalar |
311 |
# but this is used only when no scalar attribute has been specified. |
312 |
if(len(self.__point_attribute['scalars']) != 0): |
313 |
return self._getOutput().GetPointData().GetScalars().GetRange(-1) |
314 |
elif(len(self.__cell_attribute['scalars']) != 0): |
315 |
return self._getOutput().GetCellData().GetScalars().GetRange(-1) |
316 |
else: |
317 |
raise IOError("ERROR: No scalar is available.") |
318 |
|
319 |
def _getVectorRange(self): |
320 |
""" |
321 |
Return the vector range. |
322 |
|
323 |
@rtype: Two column tuple containing numbers |
324 |
@return: Vector range |
325 |
""" |
326 |
|
327 |
# Check whether any vector is available in either point or cell data. |
328 |
# If not available, program exits. |
329 |
|
330 |
# NOTE: This check is similar to the check used in _setActiveVector |
331 |
# but this is used only when no vector attribute has been specified. |
332 |
|
333 |
# NOTE: Generally GetRange(-1) returns the correct vector range. |
334 |
# However, there are certain data sets where GetRange(-1) seems |
335 |
# to return incorrect mimimum vector although the maximum vector is |
336 |
# correct. As a result, the mimimum vector has been hard coded to 0.0 |
337 |
# to accommodate for the incorrect cases. |
338 |
if(len(self.__point_attribute['vectors']) != 0): |
339 |
vector_range = \ |
340 |
self._getOutput().GetPointData().GetVectors().GetRange(-1) |
341 |
return (0.0, vector_range[1]) |
342 |
elif(len(self.__cell_attribute['vectors']) != 0): |
343 |
vector_range = \ |
344 |
self._getOutput().GetCellData().GetVectors().GetRange(-1) |
345 |
return (0.0, vector_range[1]) |
346 |
else: |
347 |
print "\nERROR: No vector is available.\n" |
348 |
sys.exit(0) |
349 |
|
350 |
def _getTensorRange(self): |
351 |
""" |
352 |
Return the tensor range. |
353 |
|
354 |
@rtype: Two column tuple containing numbers |
355 |
@return: Tensor range |
356 |
""" |
357 |
|
358 |
# Check whether any tensor is available in either point or cell data. |
359 |
# If not available, program exits. |
360 |
|
361 |
# NOTE: This check is similar to the check used in _setActiveTensor |
362 |
# but this is used only when no tensor attribute has been specified. |
363 |
if(len(self.__point_attribute['tensors']) != 0): |
364 |
return self._getOutput().GetPointData().GetTensors().GetRange(-1) |
365 |
elif(len(self.__cell_attribute['tensors']) != 0): |
366 |
return self._getOutput().GetCellData().GetTensors().GetRange(-1) |
367 |
else: |
368 |
print "\nERROR: No tensor is available.\n" |
369 |
sys.exit(1) |
370 |
|
371 |
def _getOutput(self): |
372 |
""" |
373 |
Return the output of the data collector. |
374 |
|
375 |
@rtype: vtkUnstructuredGrid |
376 |
@return: Unstructured grid |
377 |
""" |
378 |
|
379 |
return self.__output |
380 |
|