Source code for postproc

#!/usr/bin/env python
"""
This is a script for quick Mayavi-based visualizations of finite element
computations results.

Examples
--------
The examples assume that
``python -c "import sfepy; sfepy.test('--output-dir=output-tests')"``
has been run successfully and the resulting data files are present.

- view data in output-tests/navier_stokes-navier_stokes.vtk

  $ python postproc.py output-tests/navier_stokes-navier_stokes.vtk
  $ python postproc.py output-tests/navier_stokes-navier_stokes.vtk --3d

- save a snapshot image and exit

  $ python postproc.py output-tests/diffusion-poisson.vtk -o image.png -n

- save a snapshot image without off-screen rendering and exit

  $ python postproc.py output-tests/diffusion-poisson.vtk -o image.png -n --no-offscreen

- create animation (forces offscreen rendering) from
  output-tests/diffusion-time_poisson.*.vtk

  $ python postproc.py output-tests/diffusion-time_poisson.*.vtk -a mov

- create animation (forces offscreen rendering) from
  output-tests/test_hyperelastic_TL.*.vtk

  The range specification for the displacements 'u' is required, as
  output-tests/test_hyperelastic_TL.00.vtk contains only zero
  displacements which leads to invisible glyph size.

  $ python postproc.py output-tests/test_hyperelastic_TL.*.vtk --ranges=u,0,0.02 -a mov

- same as above, but slower frame rate

  $ python postproc.py output-tests/test_hyperelastic_TL.*.vtk --ranges=u,0,0.02 -a mov --ffmpeg-options="-framerate 2"
"""
from __future__ import print_function
from __future__ import absolute_import
from argparse import ArgumentParser, Action, RawDescriptionHelpFormatter
import os
import glob

import sfepy
from sfepy.base.base import assert_, get_default, output, nm
from sfepy.postprocess.viewer import (Viewer, get_data_ranges,
                                      create_file_source)
from sfepy.postprocess.domain_specific import DomainSpecificPlot
import six

helps = {
    'debug':
    'automatically start debugger when an exception is raised',
    'filename' :
    'view image file name [default: "view.png"]',
    'output_dir' :
    'output directory for saving view images; ignored when -o option is' \
    ' given, as the directory part of the filename is taken instead' \
    ' [default: "."]',
    'no_show' :
    'do not call mlab.show()',
    'no_offscreen' :
    'force no offscreen rendering for --no-show',
    'anim_format' :
    'if set to a ffmpeg-supported format (e.g. mov, avi, mpg), ffmpeg is' \
    ' installed and results of multiple time steps are given, an animation is' \
    ' created in the same directory as the view images',
    'ffmpeg_options' :
    'ffmpeg animation encoding options (enclose in "")' \
    '[default: "%(default)s"]',

    'step' :
    'set the time step. Negative indices are allowed, -1 means the last step.'
    ' The closest higher step is used if the desired one is not available.'
    ' Has precedence over --time. [default: the first step]',
    'time' :
    'set the time. The closest higher time is used if the desired one is not'
    ' available. [default: None]',
    'watch' :
    'watch the results file for changes (single file mode only)',
    'all' :
    'draw all data (normally, node_groups and mat_id are omitted)',
    'only_names' :
    'draw only named data',
    'list_ranges' :
    'do not plot, only list names and ranges of all data',
    'ranges' :
    'force data ranges [default: automatic from data]',

    'resolution' :
    'image resolution in NxN format [default: shorter axis: 600;'\
    ' depends on layout: for rowcol it is 800x600]',
    'layout' :
    'layout for multi-field plots, one of: rowcol, colrow, row, col, row#n,' \
    'col#n, where #n is the number of plots in the specified direction ' \
    '[default: %(default)s]',
    'is_3d' :
    '3d plot mode',
    'view' :
    'camera azimuth, elevation angles, and optionally also '
    'distance and focal point coordinates (without []) as in `mlab.view()` '
    '[default: if --3d is True: "45,45", else: "0,0"]',
    'roll' :
    'camera roll angle [default: %(default)s]',
    'parallel_projection' :
    'use parallel projection',
    'fgcolor' :
    'foreground color, that is the color of all text annotation labels'
    ' (axes, orientation axes, scalar bar labels) [default: %(default)s]',
    'bgcolor' :
    'background color [default: %(default)s]',
    'colormap' :
    'mayavi2 colormap name [default: %(default)s]',
    'anti_aliasing' :
    'value of anti-aliasing [default: mayavi2 default]',

    'is_scalar_bar' :
    'show scalar bar for each data',
    'is_wireframe' :
    'show wireframe of mesh surface for each data',
    'group_names' :
    'superimpose plots of data in each group',
    'subdomains' :
    'superimpose surfaces of subdomains over each data;' \
    ' example value: mat_id,0,None,True',
    'domain_specific' :
    'domain specific drawing functions and configurations',

    'scalar_mode' :
    'mode for plotting scalars with --3d, one of: cut_plane, iso_surface,'\
    ' both [default: %(default)s]',
    'vector_mode' :
    'mode for plotting vectors, one of: arrows, norm, arrows_norm, warp_norm'\
    ' [default: %(default)s]',
    'rel_scaling' :
    'relative scaling of glyphs (vector field visualization)' \
    ' [default: %(default)s]',
    'clamping' :
    'glyph clamping mode',
    'opacity' :
    'opacity in [0.0, 1.0]. Can be given either globally'
    ' as a single float, or per module, e.g.'
    ' "wireframe=0.1,scalar_cut_plane=0.5". Possible keywords are: wireframe,'
    ' scalar_cut_plane, vector_cut_plane, surface, iso_surface,'
    ' arrows_surface, glyphs. [default: 1.0]',
    'rel_text_width' :
    'relative text annotation width [default: %(default)s]',
}

[docs]class ParseView(Action): def __call__(self, parser, namespace, value, option_string=None): vals = value.split(',') assert_(len(vals) in [2, 3, 6]) val = tuple(float(ii) for ii in vals) if len(vals) == 6: val = val[:3] + (list(val[3:]),) setattr(namespace, self.dest, val)
[docs]class ParseResolution(Action): def __call__(self, parser, namespace, value, option_string=None): if value is not None: print(value) setattr(namespace, self.dest, tuple([int(r) for r in value.split('x')]))
[docs]class ParseRanges(Action): def __call__(self, parser, namespace, value, option_string=None): if value is not None: print(value) ranges = {} for rng in value.split(':'): aux = rng.split(',') ranges[aux[0]] = (float(aux[1]), float(aux[2])) setattr(namespace, self.dest, ranges)
[docs]class ParseOpacity(Action): def __call__(self, parser, namespace, value, option_string=None): try: opacity = float(value) assert_(0.0 <= opacity <= 1.0) except: opacity = {} for vals in value.split(','): key, val = vals.split('=') val = float(val) assert_(0.0 <= val <= 1.0) opacity[key] = val setattr(namespace, self.dest, opacity)
[docs]class ParseGroupNames(Action): def __call__(self, parser, namespace, value, option_string=None): if value is not None: print(value) group_names = [tuple(group.split(',')) for group in value.split(':')] setattr(namespace, self.dest, group_names)
[docs]class ParseSubdomains(Action): def __call__(self, parser, namespace, value, option_string=None): if value is not None: print(value) aux = value.split(',') try: tmin = int(aux[1]) except ValueError: tmin = None try: tmax = int(aux[2]) except ValueError: tmax = None subdomains_args = {'mat_id_name' : aux[0], 'threshold_limits' : (tmin, tmax), 'single_color' : aux[3] == 'True'} setattr(namespace, self.dest, subdomains_args)
[docs]class ParseDomainSpecific(Action): def __call__(self, parser, namespace, value, option_string=None): if value is not None: print(value) out = {} confs = value.split(':') for conf in confs: aux = conf.split(',') var_name, fun_name = aux[:2] args = aux[2:] out[var_name] = DomainSpecificPlot(fun_name, args) setattr(namespace, self.dest, out)
[docs]def view_file(filename, filter_names, options, view=None): if view is None: if options.show: offscreen = False else: offscreen = get_default(options.offscreen, True) view = Viewer(filename, watch=options.watch, ffmpeg_options=options.ffmpeg_options, output_dir=options.output_dir, offscreen=offscreen) if options.only_names is not None: options.only_names = options.only_names.split(',') view(show=options.show, is_3d=options.is_3d, view=options.view, roll=options.roll, parallel_projection=options.parallel_projection, fgcolor=options.fgcolor, bgcolor=options.bgcolor, colormap=options.colormap, layout=options.layout, scalar_mode=options.scalar_mode, vector_mode=options.vector_mode, rel_scaling=options.rel_scaling, clamping=options.clamping, ranges=options.ranges, is_scalar_bar=options.is_scalar_bar, is_wireframe=options.is_wireframe, opacity=options.opacity, subdomains_args=options.subdomains_args, rel_text_width=options.rel_text_width, fig_filename=options.filename, resolution=options.resolution, filter_names=filter_names, only_names=options.only_names, group_names=options.group_names, step=options.step, time=options.time, anti_aliasing=options.anti_aliasing, domain_specific=options.domain_specific) else: view.set_source_filename(filename) view.save_image(options.filename) return view
[docs]def main(): parser = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter) parser.add_argument('--version', action='version', version='%(prog)s ' + sfepy.__version__) parser.add_argument('--debug', action='store_true', dest='debug', default=False, help=helps['debug']) group = parser.add_argument_group('Output Options') group.add_argument('-o', '--output', metavar='filename', action='store', dest='filename', default=None, help=helps['filename']) group.add_argument('--output-dir', metavar='directory', action='store', dest='output_dir', default=None, help=helps['output_dir']) group.add_argument('-n', '--no-show', action='store_false', dest='show', default=True, help=helps['no_show']) group.add_argument('--no-offscreen', action='store_false', dest='offscreen', default=None, help=helps['no_offscreen']) group.add_argument('-a', '--animation', metavar='<ffmpeg-supported format>', action='store', dest='anim_format', default=None, help=helps['anim_format']) group.add_argument('--ffmpeg-options', metavar='<ffmpeg options>', action='store', dest='ffmpeg_options', default='-framerate 10', help=helps['ffmpeg_options']) group = parser.add_argument_group('Data Options') group.add_argument('--step', type=int, metavar='step', action='store', dest='step', default=None, help=helps['step']) group.add_argument('--time', type=float, metavar='time', action='store', dest='time', default=None, help=helps['time']) group.add_argument('-w', '--watch', action='store_true', dest='watch', default=False, help=helps['watch']) group.add_argument('--all', action='store_true', dest='all', default=False, help=helps['all']) group.add_argument('--only-names', metavar='list of names', action='store', dest='only_names', default=None, help=helps['only_names']) group.add_argument('-l', '--list-ranges', action='store_true', dest='list_ranges', default=False, help=helps['list_ranges']) group.add_argument('--ranges', type=str, metavar='name1,min1,max1:name2,min2,max2:...', action=ParseRanges, dest='ranges', help=helps['ranges']) group = parser.add_argument_group('View Options') group.add_argument('-r', '--resolution', type=str, metavar='resolution', action=ParseResolution, dest='resolution', help=helps['resolution']) group.add_argument('--layout', metavar='layout', action='store', dest='layout', default='rowcol', help=helps['layout']) group.add_argument('--3d', action='store_true', dest='is_3d', default=False, help=helps['is_3d']) group.add_argument('--view', type=str, metavar='angle,angle[,distance[,focal_point]]', action=ParseView, dest='view', help=helps['view']) group.add_argument('--roll', type=float, metavar='angle', action='store', dest='roll', default=0.0, help=helps['roll']) group.add_argument('--parallel-projection', action='store_true', dest='parallel_projection', default=False, help=helps['parallel_projection']) group.add_argument('--fgcolor', metavar='R,G,B', action='store', dest='fgcolor', default='0.0,0.0,0.0', help=helps['fgcolor']) group.add_argument('--bgcolor', metavar='R,G,B', action='store', dest='bgcolor', default='1.0,1.0,1.0', help=helps['bgcolor']) group.add_argument('--colormap', metavar='colormap', action='store', dest='colormap', default='blue-red', help=helps['colormap']) group.add_argument('--anti-aliasing', type=int, metavar='value', action='store', dest='anti_aliasing', default=None, help=helps['anti_aliasing']) group = parser.add_argument_group('Custom Plots Options') group.add_argument('-b', '--scalar-bar', action='store_true', dest='is_scalar_bar', default=False, help=helps['is_scalar_bar']) group.add_argument('--wireframe', action='store_true', dest='is_wireframe', default=False, help=helps['is_wireframe']) group.add_argument('--group-names', type=str, metavar='name1,...,nameN:...', action=ParseGroupNames, dest='group_names', help=helps['group_names']) group.add_argument('--subdomains', type=str, metavar='mat_id_name,threshold_limits,single_color', action=ParseSubdomains, dest='subdomains_args', default=None, help=helps['subdomains']) group.add_argument('-d', '--domain-specific', type=str, metavar='"var_name0,function_name0,' \ 'par0=val0,par1=val1,...:var_name1,..."', action=ParseDomainSpecific, dest='domain_specific', default=None, help=helps['domain_specific']) group = parser.add_argument_group('Mayavi Options') group.add_argument('--scalar-mode', metavar='mode', action='store', dest='scalar_mode', default='iso_surface', help=helps['scalar_mode']) group.add_argument('--vector-mode', metavar='mode', action='store', dest='vector_mode', default='arrows_norm', help=helps['vector_mode']) group.add_argument('-s', '--scale-glyphs', type=float, metavar='scale', action='store', dest='rel_scaling', default=0.05, help=helps['rel_scaling']) group.add_argument('--clamping', action='store_true', dest='clamping', default=False, help=helps['clamping']) group.add_argument('--opacity', type=str, metavar='opacity', action=ParseOpacity, dest='opacity', help=helps['opacity']) group.add_argument('--rel-text-width', type=float, metavar='width', action='store', dest='rel_text_width', default=0.02, help=helps['rel_text_width']) parser.add_argument('filenames', nargs='+') options = parser.parse_args() if options.debug: from sfepy.base.base import debug_on_error; debug_on_error() filenames = options.filenames options.fgcolor = tuple([float(ii) for ii in options.fgcolor.split(',')]) assert_(len(options.fgcolor) == 3) options.bgcolor = tuple([float(ii) for ii in options.bgcolor.split(',')]) assert_(len(options.bgcolor) == 3) can_save = not options.show # Output dir / file names. if options.filename is None: can_save = False options.filename = 'view.png' if options.output_dir is None: options.output_dir = '.' else: options.output_dir, options.filename = os.path.split(options.filename) # Data filtering, if not options.all: filter_names = ['node_groups', 'mat_id'] else: filter_names = [] if options.anim_format is not None: # Do not call show when saving an animation. options.show = False if options.list_ranges: all_ranges = {} for ii, filename in enumerate(filenames): output('%d: %s' % (ii, filename)) file_source = create_file_source(filename) if (options.step is None) and (options.time is None): steps, _ = file_source.get_ts_info() else: if options.step is not None: step, _ = file_source.get_step_time(step=options.step) else: step, _ = file_source.get_step_time(time=options.time) steps = [step] if not len(steps): steps = [0] for iis, step in enumerate(steps): output('%d: step %d' %(iis, step)) file_source.get_step_time(step=step) source = file_source.create_source() ranges = get_data_ranges(source, return_only=True) for key, val in six.iteritems(ranges): all_ranges.setdefault(key, []).append(val[3:]) if (len(filenames) > 1) or (len(steps) > 1): output('union of ranges:') else: output('ranges:') for key, ranges in six.iteritems(all_ranges): aux = nm.array(ranges) mins = aux[:, [0, 2]].min(axis=0) maxs = aux[:, [1, 3]].max(axis=0) output(' items: %s,%e,%e' % (key, mins[0], maxs[0])) output(' norms: %s,%e,%e' % (key, mins[1], maxs[1])) else: if len(filenames) == 1: filenames = filenames[0] view = view_file(filenames, filter_names, options) if can_save: view.save_image(options.filename) if options.anim_format is not None: view.save_animation(options.filename) view.encode_animation(options.filename, options.anim_format, options.ffmpeg_options)
if __name__ == '__main__': main()