Composite Data Writers
This snippet was written by Bane Sullivan and was originally posted on ParaView’s GitLab project snippets.
The functionality using decorated VTKPythonAlgorithmBase
classes as ParaView
plugins has a composite support option for the smdomain
input property that is
incredibly simple to use with filter algorithms yet can be tricky to use for
writer algorithms.
@smproxy.writer(...)
@smproperty.input(name="TableInput", port_index=0)
@smdomain.datatype(dataTypes=["vtkTable"], composite_data_supported=True)
class MyWriter(VTKPythonAlgorithmBase):
...
This solution handles altering the given filename to save out each object in
the composite dataset separately by saving each block out in
perform_write_out
method that is repeatedly called by RequestData
explicitly.
Note that we must use the composite_data_supported=True
flag for the
@smproxy.writer(...)
declaration as well as append allowable input types
within the algorithms FillInputPortInformation
method.
# This is partially pseudo-code and is implemented in `WriterBase`
@smproxy.writer(...)
@smproperty.input(name="Input", port_index=0)
@smdomain.datatype(dataTypes=["vtkDataSet"], composite_data_supported=True)
class MyWriter(VTKPythonAlgorithmBase):
...
def FillInputPortInformation(self, port, info):
"""Allows us to save composite datasets as well.
NOTE: I only care about ``vtkMultiBlockDataSet``s
"""
info.Set(self.INPUT_REQUIRED_DATA_TYPE(), self.InputType)
info.Append(self.INPUT_REQUIRED_DATA_TYPE(), 'vtkMultiBlockDataSet')
return 1
def perform_write_out(self, inputDataObject, filename):
"""This method must be implemented. This is automatically called by
``RequestData`` for single inputs or composite inputs."""
raise NotImplementedError('perform_write_out must be implemented!')
def RequestData(self, request, inInfoVec, outInfoVec):
"""Subclasses must implement a ``perform_write_out`` method that takes an
input data object and a filename. This method will automatically handle
composite data sets.
"""
inp = self.GetInputData(inInfoVec, 0, 0)
# Handle composite datasets.
# NOTE: This only handles 'vtkMultiBlockDataSet'
if isinstance(inp, vtk.vtkMultiBlockDataSet):
num = inp.GetNumberOfBlocks()
# Create a list of filenames that vary by block index
self.set_block_filenames(num)
for i in range(num):
data = inp.GetBlock(i)
if data.IsTypeOf(self.InputType):
self.perform_write_out(data, self.get_block_filename(i))
else:
print('Invalid input block %d of type(%s)' % (i, type(data)))
# Handle single input dataset
else:
self.perform_write_out(inp, self.get_file_name())
return 1
Example
import PVGeo
from PVGeo.base import WriterBase
# This is module to import. It provides VTKPythonAlgorithmBase, the base class
# for all python-based vtkAlgorithm subclasses in VTK and decorators used to
# 'register' the algorithm with ParaView along with information about UI.
from paraview.util.vtkAlgorithm import *
from vtk.util.vtkAlgorithm import VTKPythonAlgorithmBase
import vtk
import numpy as np
from vtk.numpy_interface import dataset_adapter as dsa
###############################################################################
## Now lets use ``WriterBase`` to make a writer algorithm that ParaView can use
class WriteCellCenterData(WriterBase):
"""This writer will save a file of the XYZ points for an input dataset's
cell centers and its cell data. Use in tandom with ParaView's native CSV
writer which saves the PointData. This class was originally
implemented in `PVGeo`_ by `Bane Sullivan`_.
.. _PVGeo: http://pvgeo.org
.. _Bane Sullivan: http://banesullivan.com
"""
def __init__(self):
WriterBase.__init__(self, inputType='vtkDataSet')
self.__delimiter = ','
def PerformWriteOut(self, input_data_object, filename, object_name):
# Find cell centers
filt = vtk.vtkCellCenters()
filt.SetInputDataObject(input_data_object)
filt.Update()
centers = dsa.WrapDataObject(filt.GetOutput(0)).Points
# Get CellData
wpdi = dsa.WrapDataObject(input_data_object)
celldata = wpdi.CellData
keys = celldata.keys()
# Save out using numpy
arr = np.zeros((len(centers), 3 + len(keys)))
arr[:,0:3] = centers
for i, name in enumerate(keys):
arr[:,i+3] = celldata[name]
# Now write out the data
# Clean data titles to make sure they do not contain the delimiter
repl = '_' if self.__delimiter != '_' else '-'
for i, name in enumerate(keys):
keys[i] = name.replace(self.__delimiter, repl)
header = ('%s' % self.__delimiter).join(['X', 'Y', 'Z'] + keys)
np.savetxt(filename, arr,
header=header,
delimiter=self.__delimiter,
fmt=self.get_format(),
comments='')
# Success for pipeline
return 1
def set_delimiter(self, deli):
"""The string delimiter to use"""
if self.__delimiter != deli:
self.__delimiter = deli
self.Modified()
###############################################################################
## Now lets use ``WriterBase`` to make a writer algorithm for image data
@smproxy.writer(extensions="imgfmt", file_description="Write Custom ImageData", support_reload=False)
@smproperty.input(name="Input", port_index=0)
@smdomain.datatype(dataTypes=["vtkImageData"], composite_data_supported=True)
class WriteCustomImageData(WriterBase):
"""This is an example of how to make your own file writer!
.. _PVGeo: http://pvgeo.org
.. _Bane Sullivan: http://banesullivan.com
"""
def __init__(self):
WriterBase.__init__(self, inputType='vtkImageData')
self.__delimiter = ','
def PerformWriteOut(self, input_data_object, filename, object_name):
"""Perform the file write to the given FileName with the given data
object. The super class handles all the complicated stuff.
"""
filename = filename.split('.')
filename = '.'.join(filename[0:-1]) + '_%s.%s' % (object_name, filename[-1])
writer = vtk.vtkXMLImageDataWriter()
writer.SetFileName(filename)
writer.SetInputDataObject(input_data_object)
writer.Write()
# Success for pipeline
return 1
@smproperty.stringvector(name="FileName", panel_visibility="never")
@smdomain.filelist()
def SetFileName(self, filename):
"""Specify filename for the file to write."""
WriterBase.SetFileName(self, filename)
###############################################################################
## Now wrap the cell centers writer for use in ParaView!
@smproxy.writer(extensions="dat", file_description="Cell Centers and Cell Data", support_reload=False)
@smproperty.input(name="Input", port_index=0)
@smdomain.datatype(dataTypes=["vtkDataSet"], composite_data_supported=True)
class PVWriteCellCenterData(WriteCellCenterData):
"""The ``WriteCellCenterData`` class wrapped for use as a plugin in ParaView.
Be sure that the ``composite_data_supported`` flag is set to ``True``.
"""
def __init__(self):
WriteCellCenterData.__init__(self)
@smproperty.stringvector(name="FileName", panel_visibility="never")
@smdomain.filelist()
def SetFileName(self, filename):
"""Specify filename for the file to write."""
WriteCellCenterData.SetFileName(self, filename)
@smproperty.stringvector(name="Format", default_values='%.9e')
def set_format(self, fmt):
"""Use to set the ASCII format for the writer default is ``'%.9e'``"""
WriteCellCenterData.set_format(self, fmt)
@smproperty.stringvector(name="Delimiter", default_values=',')
def set_delimiter(self, deli):
"""The string delimiter to use"""
WriteCellCenterData.set_delimiter(self, deli)