Synopsis

ParaView is a popular application for general scientific visualization (and is installed on Snellius). Here, we provide some useful information in using it.

Loading time-varying data

ParaView supports working with time-varying data, by allowing multiple sequential data files to be loaded as a single input stream, together with control over the current time. The animation support is fairly rudimentary and also doesn't optimize memory usage. So depending on what actions you perform you might end up with all data steps loaded in memory at the same time.

If you have sequentially numbered files in a format that ParaView can read  (e.g. 0000.vtk, 0001.vtk, ...) then loading these as a time-varying dataset is easy. Simply use the normal File->Open... menu option and check if ParaView recognizes the sequence as a Group :

You can unfold the group by using the little triangle, to check if ParaView detects the sequence correctly:

If you click OK with the group selected a single reader will be added to the pipeline, that will process the sequence of files on demand. The current animation time is controlled from the tool bar at the top:

Initially, only the first time step is loaded, but if you change the time the corresponding data file will be loaded (if not already loaded).

Notes:

  • The sequence detection in the Open File dialog is automatic, but you can still load a single time step if needed: simply unfold the group and select the specific file you want to load.
  • Even though the example above uses steps that are larger than 1 (i.e. 0, 28, 56, ...) ParaView still assumes the files form a sequence. Depending on how you structure your file names this might or might not be what you want. If you need more control over the sequences then use a PVD file, as described in the next subsection.
  • The time file associated with each loaded data file is, by default, derived from the index in the sequence. So in the above example the file sequence would have times 0, 1, 2, 3, .... In case you want to associate different time values you can use a PVD file as well.
  • Some file formats fail to load (or won't vary over time) as sequences. Most notably polygonal file formats like .obj  and .ply  do not work as animated datasets. You can work around this by converting these files to ParaView polygonal files ({{.vtp}}, see the Python snippet below).

More control over sequential data reading and time values

There might be situations where the default sequence detection of ParaView is not good enough. For example, you might have stored a large set of sequential files, but only want to visualize each N-th file. Or you might to want to set more meaningful time values for each time step. Sometimes the sequence detection also simply fails to work, especially when the file name part is only numbers without any prefix.

Using a PVD file can be a good option in these cases. A PVD file is a lightweight XML-based file, which references one or more files holding the actual data to load. For each of the data files some metadata can be specified that ParaView uses. In its simplest form, listing a sequence of file name + time stamp pairs, a PVD file can look like this:

<?xml version="1.0"?>
<VTKFile type="Collection" version="0.1">
  <Collection>
    <DataSet timestep="0.00" file="data/0000.vtp"/>
    <DataSet timestep="0.25" file="data/0001.vtp"/>
    <DataSet timestep="0.50" file="data/0002.vtp"/>
    <DataSet timestep="0.75" file="data/0003.vtp"/>
    <DataSet timestep="1.00" file="data/0004.vtp"/>
  </Collection>
</VTKFile>

Notes:

  • A PVD file can only reference a specific subset of file types, specifically the XML-based VTK file formats. It therefore can also not reference files in the legacy VTK file format (.vtk ).

    You might have to convert your files to an XML-based format first, in order to reference them from a PVD file. You can use a Python snippet like this (running it either in pvpython  or pvbatch ):

    from paraview.simple import *
    
    # Convert a wavefront .obj file (containing polygonal geometry) to a ParaView .vtp file  
    for i in range(N):
        filename = "/home/..../input/file%04d.obj" % i
        reader = OpenDataFile(filename) 
        writer = CreateWriter("/home/..../output/file%04d.vtp" % i)
        writer.UpdatePipeline() 
  • The file  path is relative to the PVD file.
  • A PVD file can be easily generated using a (say) Python script, e.g. detecting all files in a given directory and writing a PVD file referencing all of them.
  • There are further options per DataSet that are described in the PVD page linked above.

The more complex Xdmf file format can be an alternative to a PVD file, especially if you're working with HDF5 files holding your data and/or you need even more control.

Saving/restoring the current state

You can save ParaView's current state to a file using File → Save State... The usual type of file is a PVSM (.pvsm) file, which is in XML format and contains the full ParaView state (pipeline, views, settings, ...). You can then later load in that state file using File → Load State... .

Note that a state file will not contain the data of any files loaded, but only stores the paths of data files used at the time the state was saved, including filter parameters.

As a PVSM file is in XML format this isn't the best state representation to use if you want to make changes. A second choice for saving ParaView's state is to a Python script. This will produce a set of Python commands that should reproduce the current ParaView state, in a similar manner as what the Python trace function provides (see next section). A Python state file can be loaded in the same was as a PVSM file.

Recording interactive commands to a Python trace

ParaView contains a useful that allows you to record the commands you perform in the user interface, to a Python script. This is called tracing. The recorded commands are in the form of a Python script. This script can be replayed to perform the exact same commands, can be altered to produce other results, run on a different system, shared with colleagues, etc. Below is an example set of commands and the corresponding Python trace.

One of the more useful things you can do with such a trace is to set up a (complex) visualization pipeline, including camera view and save to file, and then only vary the data being read. This can used, for example, for animating a time series, or to produce matching visualizations for a parameter study.

Note that a Python trace uses command from the ParaView Python API, with which a lot can be accomplished. So you can start with a Python trace and then add/edit Python code as you see fit.

Using a Python trace

You can use a Python trace in multiple ways:

  1. At ParaView GUI startup
    When starting the ParaView GUI application (paraview)  you can provide a –script trace.py  option. This will execute the commands in the script, so the state of ParaView after startup will match what is in the script.

    $ paraview --script trace.py
  2. From within the ParaView GUI
    With File→Load State... and then picking trace.py you can make ParaView execute the command trace

  3. From the command-line using pvbatch
    You can use ParaView's pvbatch  command to execute a script in batch fashion. This will not start the ParaView GUI, but will simply execute the script from the command line and then exit

    $ pvbatch trace.py
  4. From the command-line using pvpython
    ParaView comes with its own Python interpreter that is linked to the ParaView libraries, called pvpython. You can run a trace script with pvpython in the same way as you would with the normal Python interpreter:

    # Run script
    $ pvpython cosmo-render.py 
    VisRTX 0.1.6, using devices:
     0: NVIDIA GeForce GTX 970 (Total: 4.2 GB, Available: 3.8 GB)
    
    # Run script, then drop into the interactive Python prompt
    $ pvpython -i cosmo-render.py 
    VisRTX 0.1.6, using devices:
     0: NVIDIA GeForce GTX 970 (Total: 4.2 GB, Available: 3.8 GB)
    >>>

    You can even pass arguments to a trace script, using the regular sys.argv variable:

    $ cat args.py
    import sys
    print('ARGV', sys.argv)
    
    $ pvpython args.py some extra args
    ARGV ['args.py', 'some', 'extra', 'args']

    This can be used to influence the behaviour of the script, for example, which data file or timestep to load.

Example

Below is an example trace for loading a volumetric dataset, changing the representation from the default outline to volume, changing the viewpoint and saving a screenshot in 1920x1080 pixels to a PNG file. We list the actions we do in the ParaView UI, followed by the Python trace that is generated from those:

  1. We start the trace using the Tools → Start Trace menu option. A Trace Options  dialog will appear, and we will accept the default options by clicking OK (see here for info on all the options).
  2. We open a data file using the File → Open  menu, select the file and click OK 
  3. To actually load the file we click the Apply  button in the pipeline options, as usual
  4. Next, we switch the representation from Outline  to Volume 
  5. We change the camera viewpoint to nicely position and orient the volume in the way we want
  6. We save a render of the current view using File → Save Screenshot..., filling in volume.png  as file name. In the subsequent Save Screenshot Options  dialog we set the image resolution to 1920x1080 pixels, leaving all other options at their current values.
  7. Finally, we stop the tracing using Tools → Stop Trace .

After stopping the trace (step 7), ParaView will show the Python trace recorded in a separate window. From there you can save it to file.

For the example steps above the full trace is listed below. It contains quite a bit more than just the actions we performed. However, we can fairly easily see where most of the steps above are performed. We added some comments prefixed with NOTE  for this (the other comment lines are written by ParaView).

# trace generated using paraview version 5.10.1
#import paraview
#paraview.compatibility.major = 5
#paraview.compatibility.minor = 10

#### import the simple module from the paraview
from paraview.simple import *
#### disable automatic camera reset on 'Show'
paraview.simple._DisableFirstRenderCameraReset()

# create a new 'Legacy VTK Reader'
# NOTE: Step 2, loading the file
cosmogridss002128vtk = LegacyVTKReader(registrationName='cosmogrid-ss002.128.vtk', FileNames=['/home/melis/data/cosmogrid-ss002.128.vtk'])

# get active view
renderView1 = GetActiveViewOrCreate('RenderView')

# show data in view
cosmogridss002128vtkDisplay = Show(cosmogridss002128vtk, renderView1, 'UniformGridRepresentation')

# trace defaults for the display properties.
cosmogridss002128vtkDisplay.Representation = 'Outline'
cosmogridss002128vtkDisplay.ColorArrayName = ['POINTS', '']
cosmogridss002128vtkDisplay.SelectTCoordArray = 'None'
cosmogridss002128vtkDisplay.SelectNormalArray = 'None'
cosmogridss002128vtkDisplay.SelectTangentArray = 'None'
cosmogridss002128vtkDisplay.OSPRayScaleArray = 'density'
cosmogridss002128vtkDisplay.OSPRayScaleFunction = 'PiecewiseFunction'
cosmogridss002128vtkDisplay.SelectOrientationVectors = 'None'
cosmogridss002128vtkDisplay.ScaleFactor = 0.0992124
cosmogridss002128vtkDisplay.SelectScaleArray = 'density'
cosmogridss002128vtkDisplay.GlyphType = 'Arrow'
cosmogridss002128vtkDisplay.GlyphTableIndexArray = 'density'
cosmogridss002128vtkDisplay.GaussianRadius = 0.00496062
cosmogridss002128vtkDisplay.SetScaleArray = ['POINTS', 'density']
cosmogridss002128vtkDisplay.ScaleTransferFunction = 'PiecewiseFunction'
cosmogridss002128vtkDisplay.OpacityArray = ['POINTS', 'density']
cosmogridss002128vtkDisplay.OpacityTransferFunction = 'PiecewiseFunction'
cosmogridss002128vtkDisplay.DataAxesGrid = 'GridAxesRepresentation'
cosmogridss002128vtkDisplay.PolarAxes = 'PolarAxesRepresentation'
cosmogridss002128vtkDisplay.ScalarOpacityUnitDistance = 0.013530780908728068
cosmogridss002128vtkDisplay.OpacityArrayName = ['POINTS', 'density']
cosmogridss002128vtkDisplay.IsosurfaceValues = [2.535883903503418]
cosmogridss002128vtkDisplay.SliceFunction = 'Plane'
cosmogridss002128vtkDisplay.Slice = 63

# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction'
cosmogridss002128vtkDisplay.ScaleTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 5.071767807006836, 1.0, 0.5, 0.0]

# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction'
cosmogridss002128vtkDisplay.OpacityTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 5.071767807006836, 1.0, 0.5, 0.0]

# init the 'Plane' selected for 'SliceFunction'
cosmogridss002128vtkDisplay.SliceFunction.Origin = [0.496062, 0.496062, 0.496062]

# reset view to fit data
renderView1.ResetCamera(False)

# get the material library
materialLibrary1 = GetMaterialLibrary()

# update the view to ensure updated data information
renderView1.Update()

# set scalar coloring
ColorBy(cosmogridss002128vtkDisplay, ('POINTS', 'density'))

# rescale color and/or opacity maps used to include current data range
cosmogridss002128vtkDisplay.RescaleTransferFunctionToDataRange(True, True)

# NOTE: Step 4, setting the representation to Volume
# change representation type
cosmogridss002128vtkDisplay.SetRepresentationType('Volume')

# get color transfer function/color map for 'density'
densityLUT = GetColorTransferFunction('density')

# get opacity transfer function/opacity map for 'density'
densityPWF = GetOpacityTransferFunction('density')

# get layout
layout1 = GetLayout()

# NOTE: This is the resolution of the 3D render view within the ParaView UI
# layout/tab size in pixels
layout1.SetSize(1462, 755)

# NOTE: Step 5, changing the camera viewpoint
# current camera placement for renderView1
renderView1.CameraPosition = [1.6811449463099066, 1.9828224086861659, 3.2173866078476068]
renderView1.CameraFocalPoint = [0.4960620105266569, 0.49606201052665677, 0.4960620105266572]
renderView1.CameraViewUp = [-0.16693749226315047, 0.894029726981841, -0.41574357595718253]
renderView1.CameraParallelScale = 0.8592046059369374

# NOTE: Step 6, saving the screenshot
# save screenshot
SaveScreenshot('/home/melis/volume.png', renderView1, ImageResolution=[1920, 1080])

#================================================================
# addendum: following script captures some of the application
# state to faithfully reproduce the visualization during playback
#================================================================

#--------------------------------
# saving layout sizes for layouts

# layout/tab size in pixels
layout1.SetSize(1462, 755)

#-----------------------------------
# saving camera placements for views

# current camera placement for renderView1
renderView1.CameraPosition = [1.6811449463099066, 1.9828224086861659, 3.2173866078476068]
renderView1.CameraFocalPoint = [0.4960620105266569, 0.49606201052665677, 0.4960620105266572]
renderView1.CameraViewUp = [-0.16693749226315047, 0.894029726981841, -0.41574357595718253]
renderView1.CameraParallelScale = 0.8592046059369374

#--------------------------------------------
# uncomment the following to render all views
# RenderAllViews()
# alternatively, if you want to write images, you can use SaveScreenshot(...).

Some remarks:

  • There is no "clear state" command, so any actions performed in the script are played back on top of the current state.
  • If we had a time-varying dataset loaded and an animation set up (e.g. a camera orbit) we could in step 6 also use File → Save Animation.... This would then generate a set of images (or a single video file) from the trace.