Source code for espressomd.io.vtk
#
# Copyright (C) 2023 The ESPResSo project
#
# This file is part of ESPResSo.
#
# ESPResSo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ESPResSo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import numpy as np
import vtk
import vtk.util.numpy_support
[docs]
class VTKReader:
"""
Reader for VTK multi-piece uniform grids written in XML format.
"""
error_tolerance = 1e-5 # VTK data is written with 1e-7 precision
[docs]
@classmethod
def get_array_names(cls, reader):
array_names = set()
n_ghost_layers = reader.GetUpdateGhostLevel()
n_pieces = reader.GetNumberOfPieces()
for piece_index in range(n_pieces):
reader.UpdatePiece(piece_index, n_pieces, n_ghost_layers)
piece = reader.GetOutput()
cell = piece.GetCellData()
for i in range(cell.GetNumberOfArrays()):
array_names.add(cell.GetArrayName(i))
return array_names
[docs]
@classmethod
def get_piece_topology(
cls, piece, array, bounding_box_lower, bounding_box_upper):
bounds = np.array(piece.GetBounds())
box_l = bounds[1::2] - bounds[0:-1:2]
n_grid_points = array.GetNumberOfTuples()
shape_float = box_l / np.min(box_l)
shape_float *= np.cbrt(n_grid_points / np.prod(shape_float))
shape_int = np.around(shape_float).astype(int)
assert np.linalg.norm(shape_int - shape_float) < cls.error_tolerance and np.prod(
shape_int) == n_grid_points, "only cubic grids are supported"
agrid = np.mean(box_l / shape_float)
shape = tuple(shape_int.tolist())
lower_corner = []
for i in range(3):
start = int(np.around(bounds[i * 2]))
stop = start + shape[i]
bounding_box_lower[i] = min(bounding_box_lower[i], start)
bounding_box_upper[i] = max(bounding_box_upper[i], stop)
lower_corner.append(start)
return agrid, shape, lower_corner
[docs]
@classmethod
def reconstruct_array(cls, reader, array_name):
n_pieces = reader.GetNumberOfPieces()
n_ghost_layers = reader.GetUpdateGhostLevel()
# get bounding box
info = []
agrids = []
bounding_box_lower = 3 * [float("inf")]
bounding_box_upper = 3 * [-float("inf")]
for piece_index in range(n_pieces):
reader.UpdatePiece(piece_index, n_pieces, n_ghost_layers)
piece = reader.GetOutput()
cell = piece.GetCellData()
array = cell.GetArray(array_name)
if array is not None:
agrid, shape, lower_corner = cls.get_piece_topology(
piece, array, bounding_box_lower, bounding_box_upper)
agrids.append(agrid)
info.append([piece_index, shape, lower_corner])
if not info:
return None
# get array type and size
assert float("inf") not in bounding_box_lower
assert -float("inf") not in bounding_box_upper
if np.std(agrids) / np.mean(agrids) > cls.error_tolerance:
raise NotImplementedError(
f"VTK non-uniform grids are not supported (got agrid = {agrids} when parsing array '{array_name}')")
data_dims = np.array(bounding_box_upper) - np.array(bounding_box_lower)
piece_index = info[0][0]
reader.UpdatePiece(piece_index, n_pieces, n_ghost_layers)
array = reader.GetOutput().GetCellData().GetArray(array_name)
vector_length = array.GetNumberOfComponents()
val_dims = [] if vector_length == 1 else [vector_length]
data_type = array.GetDataTypeAsString()
if data_type == "float":
dtype = float
elif data_type == "int":
dtype = int
else:
raise NotImplementedError(
f"Unknown VTK data type '{data_type}' (when parsing array '{array_name}')")
# get data
data = np.empty(data_dims.tolist() + val_dims, dtype=dtype)
for piece_index, shape, lower_corner in info:
reader.UpdatePiece(piece_index, n_pieces, n_ghost_layers)
array = reader.GetOutput().GetCellData().GetArray(array_name)
subset = []
for i in range(3):
start = lower_corner[i] - bounding_box_lower[i]
stop = start + shape[i]
subset.append(slice(start, stop))
data[tuple(subset)] = vtk.util.numpy_support.vtk_to_numpy(
array).reshape(list(shape) + val_dims, order='F')
return data
[docs]
def parse(self, filepath):
reader = vtk.vtkXMLUnstructuredGridReader()
reader.SetFileName(str(filepath))
reader.Update()
arrays = {}
array_names = self.get_array_names(reader)
for array_name in sorted(array_names):
arrays[array_name] = self.reconstruct_array(reader, array_name)
return arrays