matplotlib is a Python module that can be used to make publication-quality plots. We have recently integrated matplotlib into ParaView as a new type of view called a “Python View”. This new view will be available in the ParaView 4.1 release. Below is a screenshot of the new Python View (right) next to a 3D rendering (left) of the momentum vector glyphs in the bluntfin.vts example dataset. The top subplot in the Python View is a matplotlib scatterplot of momentum magnitude versus density. The bottom subplot is a matplotlib histogram of the same density data.
The Python View has a single property, a Python script that contains the matplotlib rendering commands. All the Python bindings for ParaView and VTK that are available in the Python scripting module are available from within the script, making it possible to plot any array from just about any dataset that can be loaded into ParaView.
The view requires that the Python script where the plotting occurs define two functions. In the first function, you request which arrays you would like to transfer to the client for rendering (at present, all rendering in this view takes place on the client, even in client-server mode). These arrays can be point data, cell data, field data, and table row data. This function runs only on data server processes. It provides access to the underlying data object on the server so that you can query any aspect of the data using the Python-wrapped parts of VTK and ParaView.
The second function is where you put matplotlib rendering commands. This function runs only on the ParaView client. It has access to the complete data object gathered from the data server nodes, but only the arrays requested in the first function. This component will typically set up one or more matplotlib subplots, convert data from VTK to a form that can be passed to matplotlib, and plot the data.
Selecting data arrays to plot
Because a dataset residing on the ParaView server may be large, transferring all the data to the client may not be possible or practical. For that reason, we have provided a mechanism to select which data arrays to transfer to the client. The overall structure of the data object, however, including cell connectivity, point positions, and hierarchical block structure, is always transferred to the client.
The Python script for the view must define a function called setup_data(view). The “view” argument is the VTK object for the Python View. Through the view object, the current datasets loaded into ParaView may be accessed.
Here’s an example of this function that was used to generate the image above:
def setup_data(view): # Iterate over visible data objects for i in xrange(view.GetNumberOfVisibleDataObjects()): # You need to use GetVisibleDataObjectForSetup(i) in setup_data to access the data object. dataObject = view.GetVisibleDataObjectForSetup(i) # The data object has the same data type and structure as the data object that # sits on the server. You can query the size of the data, for instance, or do anything # else you can do through the Python wrapping. print "Memory size:", dataObject.GetActualMemorySize(), "kilobytes" # Clean up from previous calls here. We want to unset any of the arrays requested # in previous calls to this function. view.DisableAllAttributeArrays() # By default, no arrays will be passed to the client. You need to explicitly request # the arrays you want. Here, we'll request the Density point data array view.SetAttributeArrayStatus(i, vtkDataObject.POINT, "Density", 1) view.SetAttributeArrayStatus(i, vtkDataObject.POINT, "Momentum", 1) # Other attribute arrays can be set similarly view.SetAttributeArrayStatus(i, vtkDataObject.FIELD, "fieldData", 1)
The vtkPythonView class passed in as the “view” argument to setup_data(view) defines several methods useful for specifying which data arrays to copy:
- GetNumberOfVisibleDataObjects() – Returns the number of visible data objects in the view. If an object is not visible, it should not show up in the rendering, so all the methods provided by the view deal only with visible objects.
- GetVisibleDataObjectForSetup(visibleObjectIndex) – This returns the visibleObjectIndex-th visible data object in the view (the data object will have an open eye next to it in the pipeline browser).
- GetNumberOfAttributeArrays(visibleObjectIndex, attributeType) – Returns the number of attribute arrays for the visibleObjectIndex-th visible object and the given attribute type (e.g., vtkDataObject.POINT, vtkDataObject.CELL, etc.).
- GetAttributeArrayName(visibleObjectIndex, attributeType, arrayIndex) – Returns the name of the array of the given attribute type at the given array index for the visibleObjectIndex-th object.
- SetAttributeArrayStatus(visibleObjectIndex, vtkDataObject.POINT, “Density”, 1) – Sets the array status of an attribute array. The first argument is the visible object index, the second object is the attribute association of the array, the third argument is the name of the array, and the last argument specifies if the array is to be copied (1) or not (0).
- GetAttributeArrayStatus(visibleObjectIndex, vtkDataObject.POINT, “Density”) – Retrieves the array status for the object with the given visible index with a given attribute association (second argument) and name (last argument).
- DisableAllAttributeArrays() – Set all arrays to not be copied.
After the setup_data(view) function has been called, ParaView will transfer the data object and selected arrays to the client. When that is done, it will call the render(view, figure) function you have defined in your script.
The “view” argument to the render(view, figure) function is the vtkPythonView object on the client. The “figure” argument is a matplotlib FigureCanvasAgg object that is automatically created by vtkPythonView. This figure object is the hook into the matplotlib library.
The methods in the vtkPythonView class that are useful within the render function are:
- GetNumberOfVisibleDataObjects() – Returns the number of visible data objects in the view.
- GetVisibleDataObjectForRendering(visibleObjectIndex) – Returns the dataset that has been transferred to the client with the selected arrays.
def render(view, figure): # Set the border color of the figure figure. set_facecolor('white') # Set up a new subplot within the given figure ax1 = figure.add_subplot(2,1,1) ax1.minorticks_on() ax1.set_title('Momentum magnitude vs. density') ax1.set_xlabel('Density') ax1.set_ylabel('Momentum magnitude') ax2 = figure.add_subplot(2,1,2) ax2.set_title('Density histogram') # Iterate over visible data objects for i in xrange(view.GetNumberOfVisibleDataObjects()): # You need to use GetVisibleDataObjectForRendering(i) in this function because # it returns the copy of the data on the client, and this function runs only on the client. dataObject = view.GetVisibleDataObjectForRendering(i) # Now plot a 2D histogram of momentum magnitude vs. density density = dataObject.GetPointData().GetArray("Density") npDensity = ns.vtk_to_numpy(density) momentum = dataObject.GetPointData().GetArray("Momentum") npMomentum = ns.vtk_to_numpy(momentum) npMomentumAbs = sqrt(npMomentum[:,0]**2 + npMomentum[:,1]**2 + npMomentum[:,2]**2) ax1.scatter(npDensity, npMomentumAbs) ax2.hist(npDensity,bins=30)
One of the first steps in this render function involves obtaining a matplotlib Axes object as a subplot in the figure. The Axes object provides many of the actual plotting commands in matplotlib. For a complete set of plot types available, please see the Axes documentation.
Enabling Python View in ParaView
To enable the Python View in ParaView, configure ParaView in CMake with the PARAVIEW_ENABLE_PYTHON and PARAVIEW_ENABLE_MATPLOTLIB options set to ON. You will also need to have matplotlib installed on your system.
The pre-built ParaView binaries include matplotlib 1.1. Some features available in newer versions will not be available if you are using the these binaries.
For multiblock datasets, the methods GetNumberOfAttributeArrays(…) and GetAttributeArrayName(…) should not be used. Instead, you should query the individual blocks through the vtkMultiblockDataSet API to determine the available attribute array names. You can do this directly on the data object returned with GetVisibleDataObjectForSetup(…). You will still need to use SetAttributeArrayStatus(…) to select which arrays to copy; any block with an array with the type and name given in the SetAttributeArrayStatus(…) will have that array copied to the client. At this time, there is no per-block array selection mechanism available.
Plotting with matplotlib is not currently available for datasets stored as vtkImageData objects because there is limited support for distributing and reassembling these objects in parallel in ParaView. As a workaround, you can convert vtkImageData datasets into vtkUnstructuredGrids using the “Append Datasets” filter and plot the output with matplotlib.
ParaView now makes it possible to view plots made with matplotlib side-by-side with ParaView’s existing view types in a single window. To try out matplotlib in ParaView yourself, you can get started by downloading the statefile attached to this post. To get the bluntfin.vts dataset, you will need the data you can download from the ParaView downloads page by selecting Data, Documentation, and Tutorials.
Update: The original state file for this post was lost when we transitioned from our previous blog software. Please download this state file recreated for this post using ParaView 5.5.2: BluntfinMatplotlib.