Peano
Loading...
Searching...
No Matches
Visualiser.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3from peano4.visualisation.input.PatchFileParser import PatchFileParser
4
5
7 u_cell_data,
8 u_dof,
9 u_dimensions,
10 u_unknowns,
11 u_is_data_associated_to_cell,
12 u_description,
13 u_mapping,
14):
15 """
16 All the parsers in a data set have to yield the same type of data, i.e.
17 with the same number of unknowns and dofs/patch. I return the crucial
18 data afterwards, i.e. this routine validates and returns the quantities
19 of interest.
20 """
21
22 # These are the data to be returned
23 dimensions = -1
24 unknowns = -1
25 dof = -1
26 description = ""
27 is_data_associated_to_cell = True
28 mapping = ""
29
30 # Take copies
31 (
32 snapshot_cell_data,
33 snapshot_dof,
34 snapshot_dimensions,
35 snapshot_unknowns,
36 is_data_associated_to_cell,
37 description,
38 mapping,
39 ) = (
40 u_cell_data,
41 u_dof,
42 u_dimensions,
43 u_unknowns,
44 u_is_data_associated_to_cell,
45 u_description,
46 u_mapping,
47 )
48
49 if snapshot_dimensions not in [2, 3]:
50 print("File parsing unsuccessful. Supported dimensions are d=2 and d=3")
51 return
52 if snapshot_dof == 0:
53 print("File parsing unsuccessful. No dof specified")
54 return
55 if snapshot_unknowns == 0:
56 print("File parsing unsuccessful. No unknowns specified")
57 return
58
59 if snapshot_dimensions != dimensions and dimensions > 0:
60 print(
61 "Dimensions not compatible with dimensions from previous files in the snapshot"
62 )
63 return
64 dimensions = snapshot_dimensions
65 if snapshot_dof != dof and dof > 0:
66 print("DoF not compatible with dof from previous files in the snapshot")
67 return
68 dof = snapshot_dof
69
70 if snapshot_unknowns != unknowns and unknowns > 0:
71 print(
72 "Unknowns not compatible with unknowns from previous files in the snapshot"
73 )
74 return
75 unknowns = snapshot_unknowns
76 return (
77 snapshot_cell_data,
78 dof,
79 dimensions,
80 unknowns,
81 is_data_associated_to_cell,
82 description,
83 mapping,
84 )
85
86
88 """!
89 Small class to capture all of the snapshots in a Patch meta file.
90 Just need the timestamp this is associated with and all of the
91 patch files that this file includes. Add them using the right
92 member function.
93 """
94
95 def __init__(self):
96 self.timestamp = -1
97 self.filenames = []
98
99 def add_patch_file(self, patch_file_name):
100 """!"""
101 # For debugging whilst in testing phase
102 assert patch_file_name not in self.filenames, "Already added this patch file!"
103 self.filenames.append(patch_file_name)
104
105 @property
107 """!
108 Property to indicate number of patch files in this snapshot
109 """
110 return len(self.filenames)
111
112 def set_timestamp(self, timestamp):
113 assert self.timestamp == -1, "Should only set this property once!"
114 self.timestamp = timestamp
115
116 def __repr__(self):
117 return f"Timestamp: {self.timestamp}, {self.include_file_counter} subdomains"
118
119
121 """!
122 Helper class to capture the important data coming from a patch file
123
124 This is what we use to capture the combined patch file data from.
125 We could keep everything in the helper class from PatchFileParser,
126 but the intention during the rewrite was to keep everything fairly
127 close to the way the original vis scripts worked.
128 """
129
130 def __init__(self, unknown_name, dimensions):
131 self._dimensions = dimensions
132 self._unknown_name = unknown_name
133 self._cell_data = []
134 self._dof = 0
135 self._unknowns = 0
137 self._description = ""
138 self._mapping = None
139
140 def copy_data_from_parser_object(self, unknown_attributes):
141 """!
142 pass in a UnknownAttributes object from PatchFileParser.py,
143 steal everything and return
144 """
145 assert (
146 self._unknown_name == unknown_attributes.unknown_name
147 ), "Comparing the wrong objects. something must have gone wrong..."
148 # Append the cell data to this
149 self._cell_data += unknown_attributes.cell_data
150
151 # Make sure all other attributes are the same
152 self._dof = unknown_attributes.dof
153 # self._dimensions = unknown_attributes.dimensions # This one was not captured
154 self._unknowns = unknown_attributes.unknowns
155 self._is_data_associated_to_cell = unknown_attributes.is_data_associated_to_cell
156 self._description = unknown_attributes.description
157 self._mapping = unknown_attributes.mapping
158
159 def apply_renderer_to_data(self, renderer):
160 """!
161 Pass in a renderer object which will modify everything
162 in the way that the filter.render() function intends
163
164 This is basically the same as render_or_validate_for_each_unknown() from
165 the PatchFileParser class. The reason why we don't need
166 to do it for each unknown here is that the different unknowns
167 have been captured in by the caller of this function
168
169 We could rename this to something more general, since i
170 think we modify the same attributes in the same order
171 as the validate function
172 """
173 (
174 self._cell_data,
175 self._dof,
176 self._dimensions,
177 self._unknowns,
179 self._description,
180 self._mapping,
181 ) = renderer(
182 self._cell_data,
183 self._dof,
184 self._dimensions,
185 self._unknowns,
187 self._description,
188 self._mapping,
189 )
190
191 def __repr__(self):
192 return f"{self._unknown_name}: {self._unknowns} unknowns"
193
194
196 """!
197 The visualiser is first and foremost a persistency layer around
198 datasets. It serves as abstract base class for real output formats,
199 i.e. it can parse files and hold them, but it does not do anything
200 with them.
201
202 The flow should work like this:
203 - Pass the file name of the patch file meta data to this class.
204 We then use read_metadata() to read this file, and gather, for
205 each timestamp, the list of patch files that we need to read.
206 For each timestamp, we create an instance of the Snapshot
207 helper class, which captures the timestamp, plus a list of
208 patch files to parse
209 - We then pass back to display(). Then, for each snapshot, we
210 do the following:
211
212 - Create a PatchFileParser object for each patch file within
213 this timestamp
214 - Read all the patch file data and combine it into the
215 PatchFileData class in this file.
216 - Return this data to child class, so that it can be rendered
217 by display()
218
219 Currently, the only working child class is the VTU renderer.
220 """
221
222 def __init__(self, file_name, start, end, verbose=False):
223 """!
224 Abstract superclass of a visualiser
225
226 file_name: String
227 Name of a Peano patch file. This does not
228 need to be a meta file.
229
230 If we run render.py with the argument -s (to denote we are using single
231 file), then we will use the file_name to create a member of the
232 snapshot class (which captures the patch file to read, with default
233 timestamp of 0). This is then parsed by parse_and_render, and handed
234 back to child class.
235
236 If this is a meta file, then display() will be used.
237
238 verbose: Boolean
239 Controls how much the class writes out.
240
241 ## Attributes
242
243 self.identifier: String
244 Pick one subidentifier from the input file. If this one is empty, we take
245 every input data set. However, some output formats (such as ParaView)
246 don't support multiple output data sets, so we might end up with either
247 the first or last one from the file - depending on the implementations.
248 """
249 self._file_name = file_name # Either for meta file or single file we pass in
250 self._start = start
251 self._end = end
252 self._filter = []
253
254 # This is used for writing patch files
255 self.identifier = (
256 "" # This identifier is also used in helper class. pass it along
257 )
258 self.verbose = verbose
259
260 self._dimensions = 0
261 self._description = "" #
263
264 # Holds all of the timestamps and file names of
265 # all the snapshots that we wanna visualise
266 self.snapshots = []
267
268 # for debugging
270
271 def display(self):
272 """
273 This is the one routine the visualisers typically do overwrite.
274 """
275 # First, read metadata file. Won't be doing this for single file
276 self.read_metadata()
277 return
278
280 """!
281 If we are here we've supplied a single file which is
282 itself a snapshot. Turn it into an instance of the
283 snapshot helper class, set the timestamp to 0 and
284 pass it back. We can just call parse_and_render_snapshot()
285 on the result
286 """
287 snapshot = SnapShot()
288 snapshot.add_patch_file(self._file_name)
289 snapshot.set_timestamp(0)
290 return snapshot
291
292 def read_metadata(self):
293 """!
294 Read the patch file metadata. In this routine we should:
295 - Count the number of timesteps this patch file records
296 - Get each of the file paths for the timestep
297 """
298 found_snapshot = False
299 found_snapshots = []
300
301 print(f"Reading metadata for {self._file_name}")
302 with open(self._file_name, "r") as f:
303 line = f.readline()
304 while line: # While there is another line to read, increment at bottom
305 if "begin dataset" in line:
306 # Encountered a dataset. Get the snapshot and all the filenames associated
307
308 # Create a new snapshot instance
309 s = SnapShot()
310 while (
311 "end dataset" not in line
312 ): # Read until the end of the dataset
313 if "timestamp" in line:
314 s.set_timestamp(float(list(line.split(" "))[2][:-1]))
315
316 if "include" in line:
317 file_name = line.split('"')[1]
318 # print(f"Adding {file_name} to snapshot")
319 s.add_patch_file(file_name)
320 line = f.readline()
321
322 # We have left the while loop for dataset. add snapshot to list
323 found_snapshots.append(s)
324 found_snapshot = True
325 line = f.readline()
326
327 assert (
328 found_snapshot
329 ), "No snapshots found. Did you mean to run just for a single file? If so, try using the argument -s"
330
331 # Remove when working
332 self.meta_data_has_been_read = True
333 self.snapshots = found_snapshots[self._start : self._end + 1]
334 for snapshot in self.snapshots:
335 print(f"Got snapshot: {snapshot}")
336
337 def append_filter(self, filter):
338 """ """
339 self._filter.append(filter)
340
341 def remove_filters(self):
342 """ """
343 self._filter = []
344
345 def parse_and_render_snapshot(self, snapshot):
346 """!
347 We pass in a snapshot object here, which should contain
348 time stamp and filenames for everything we want to render here.
349
350 Behaviour is:
351 - parse all the files listed in snapshot (use the method we wrote)
352 - return a dict, where the keys are unknown names, and the values are cell data to be rendered
353 - child class should handle the rest.
354 """
355
356 data_to_be_rendered = self.parse_snapshot(snapshot)
357
358 # Hand back to child class
359 return data_to_be_rendered
360
361 def parse_snapshot(self, snapshot):
362 """!
363 We rewrite "reload" from previous version of this file.
364
365 We take in a argument of "snapshot", which is a helper class
366 with two attributes: timestamp and a list of patch files to parse.
367
368 We need to ensure that self.identifier is set correctly so that
369 the correct unknown is picked by the PatchFileParser. Remove this
370 line of comment when this is done.
371
372 Also need to handle removal of relative file paths here
373
374 The reason we (possibly) apply filters on individual pieces of data,
375 or once the patches have been concatenated is because we may have
376 more than one file to parse per snapshot. The choice is is about
377 whether we want to apply filters before combining all the patches
378 into one list, or afterwards?
379
380 What does this function do?
381 - Get filename for each patch file in this timestamp
382 - Produce a PatchFileParser object for each
383 - Parse each file in parallel
384 - Apply all the filters specified in render.py
385 - Amalgamate data as in original reload() method
386 - Returns a dict, where the keys are unknown names, and the values are patches that can be placed into individual patch files
387 """
388 parsers = []
389
390 for file_counter, patch_filename in enumerate(snapshot.filenames):
391 if self.verbose:
392 print(f"Parsing file {patch_filename}")
393 parser = PatchFileParser(patch_filename, self.identifier, file_counter)
394 parsers.append(parser)
395
396 for parser in parsers:
397 parser.parse_file()
398
399 if self.verbose:
400 print("All individual files are parsed")
401 print("Apply filter(s) to individual files (where appropriate)")
402
403 for parser in parsers:
404 for fil in self._filter:
405 if fil.run_on_individual_pieces_of_data:
406 if self.verbose:
407 print(f"Apply filter {fil} to snapshot")
408 # We modify the attributes of parser here, and amalgamate after.
409
410 # Let's hide what it does.
411 # Pass in the function to be evaluated. Handle each of the unknowns in patch file parser class
412
413 # By default, we don't go here.
414 parser.render_or_validate_for_each_unknown(fil.render)
415
416 # Let's create a dict of pairs unknown_name, patch_file_data
417 patch_file_data_to_render = {}
418
419 # Let's get all of the unknown names seen by the parsers
420 # This is obviously redundant, but let's not assume (at this stage)
421 # that all of the unknown names have been seen in all of the patch files
422 # for this snapshot.
423 for parser in parsers:
424 for unknown_name, unknown_data in parser.unknown_attributes.items():
425 # ie we create empty helper class with the unknown name
426 if unknown_name not in patch_file_data_to_render.keys():
427 # Not seen it yet, add for first time.
428 patch_file_data_to_render[unknown_name] = PatchFileData(
429 unknown_name, unknown_data.dimensions
430 )
431 # Steal data
432 # This will amalgamate cell data on a like-for-like basis
433 patch_file_data_to_render[unknown_name].copy_data_from_parser_object(
434 parser.unknown_attributes[unknown_name]
435 )
436
437 # Now we have amalgamated all of the patch data, let's apply the filters
438 for unknown_name, patch_file_data in patch_file_data_to_render.items():
439 for fil in self._filter:
440 if fil.run_on_concatenated_data:
441 # Pass this function in, rest is handled inside
442 patch_file_data.apply_renderer_to_data(fil.render)
443 if self.verbose:
444 print(
445 f"Parsing {unknown_name} complete. Found {len(patch_file_data._cell_data)} cells, {patch_file_data._dof} dofs/cell with {patch_file_data._unknowns} unknowns per dof"
446 )
447
448 for parser in parsers:
449 # Pass the validate function along to validate that the
450 # attributes have been correctly set.
451 parser.render_or_validate_for_each_unknown(validate)
452 return patch_file_data_to_render
#define validate(booleanExpr)
Definition TestMacros.h:37
Helper class to capture the important data coming from a patch file.
copy_data_from_parser_object(self, unknown_attributes)
pass in a UnknownAttributes object from PatchFileParser.py, steal everything and return
apply_renderer_to_data(self, renderer)
Pass in a renderer object which will modify everything in the way that the filter....
Small class to capture all of the snapshots in a Patch meta file.
Definition Visualiser.py:87
include_file_counter(self)
Property to indicate number of patch files in this snapshot.
The visualiser is first and foremost a persistency layer around datasets.
parse_snapshot(self, snapshot)
We rewrite "reload" from previous version of this file.
__init__(self, file_name, start, end, verbose=False)
Abstract superclass of a visualiser.
read_metadata(self)
Read the patch file metadata.
display(self)
This is the one routine the visualisers typically do overwrite.
parse_and_render_snapshot(self, snapshot)
We pass in a snapshot object here, which should contain time stamp and filenames for everything we wa...
display_single_file(self)
If we are here we've supplied a single file which is itself a snapshot.