你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-08 07:24:19 +08:00
Split up VTKHDF classes into seperate files
这个提交包含在:
@@ -29,7 +29,9 @@ from tqdm import tqdm
|
||||
|
||||
import gprMax.config as config
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.vtkhdf import VtkCellType, VtkImageData, VtkUnstructuredGrid
|
||||
from gprMax.vtkhdf_filehandlers.vtk_image_data import VtkImageData
|
||||
from gprMax.vtkhdf_filehandlers.vtk_unstructured_grid import VtkUnstructuredGrid
|
||||
from gprMax.vtkhdf_filehandlers.vtkhdf import VtkCellType
|
||||
|
||||
from ._version import __version__
|
||||
from .cython.geometry_outputs import get_line_properties
|
||||
|
@@ -0,0 +1,184 @@
|
||||
from os import PathLike
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from mpi4py import MPI
|
||||
|
||||
from gprMax.vtkhdf_filehandlers.vtkhdf import VtkHdfFile
|
||||
|
||||
|
||||
class VtkImageData(VtkHdfFile):
|
||||
"""File handler for creating a VTKHDF Image Data file.
|
||||
|
||||
File format information is available here:
|
||||
https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#image-data
|
||||
"""
|
||||
|
||||
DIRECTION_ATTR = "Direction"
|
||||
ORIGIN_ATTR = "Origin"
|
||||
SPACING_ATTR = "Spacing"
|
||||
WHOLE_EXTENT_ATTR = "WholeExtent"
|
||||
|
||||
DIMENSIONS = 3
|
||||
|
||||
@property
|
||||
def TYPE(self) -> Literal["ImageData"]:
|
||||
return "ImageData"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, PathLike],
|
||||
shape: npt.NDArray[np.intc],
|
||||
origin: Optional[npt.NDArray[np.single]] = None,
|
||||
spacing: Optional[npt.NDArray[np.single]] = None,
|
||||
direction: Optional[npt.NDArray[np.single]] = None,
|
||||
comm: Optional[MPI.Comm] = None,
|
||||
):
|
||||
"""Create a new VtkImageData file.
|
||||
|
||||
If the file already exists, it will be overriden. Required
|
||||
attributes (Type and Version) will be written to the file.
|
||||
|
||||
The file will be opened using the 'mpio' h5py driver if an MPI
|
||||
communicator is provided.
|
||||
|
||||
Args:
|
||||
filename: Name of the file (can be a file path). The file
|
||||
extension will be set to '.vtkhdf'.
|
||||
shape: Shape of the image data to be stored in the file.
|
||||
This specifies the number of cells. Image data can be
|
||||
1D, 2D, or 3D.
|
||||
origin (optional): Origin of the image data. Default
|
||||
[0, 0, 0].
|
||||
spacing (optional): Discritisation of the image data.
|
||||
Default [1, 1, 1].
|
||||
direction (optional): Array of direction vectors for each
|
||||
dimension of the image data. Can be a flattened array.
|
||||
I.e. [[1, 0, 0], [0, 1, 0], [0, 0, 1]] and
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1] are equivalent. Default
|
||||
[[1, 0, 0], [0, 1, 0], [0, 0, 1]].
|
||||
comm (optional): MPI communicator containing all ranks that
|
||||
want to write to the file.
|
||||
"""
|
||||
super().__init__(filename, comm)
|
||||
|
||||
if len(shape) == 0:
|
||||
raise ValueError(f"Shape must not be empty.")
|
||||
if len(shape) > self.DIMENSIONS:
|
||||
raise ValueError(f"Shape must not have more than {self.DIMENSIONS} dimensions.")
|
||||
elif len(shape) < self.DIMENSIONS:
|
||||
shape = np.concatenate((shape, np.ones(self.DIMENSIONS - len(shape), dtype=np.intc)))
|
||||
|
||||
self.shape = shape
|
||||
|
||||
whole_extent = np.zeros(2 * self.DIMENSIONS, dtype=np.intc)
|
||||
whole_extent[1::2] = self.shape
|
||||
self._set_root_attribute(self.WHOLE_EXTENT_ATTR, whole_extent)
|
||||
|
||||
if origin is None:
|
||||
origin = np.zeros(self.DIMENSIONS, dtype=np.single)
|
||||
self.set_origin(origin)
|
||||
|
||||
if spacing is None:
|
||||
spacing = np.ones(self.DIMENSIONS, dtype=np.single)
|
||||
self.set_spacing(spacing)
|
||||
|
||||
if direction is None:
|
||||
direction = np.diag(np.ones(self.DIMENSIONS, dtype=np.single))
|
||||
self.set_direction(direction)
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> npt.NDArray[np.intc]:
|
||||
return self._get_root_attribute(self.WHOLE_EXTENT_ATTR)
|
||||
|
||||
@property
|
||||
def origin(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.ORIGIN_ATTR)
|
||||
|
||||
@property
|
||||
def spacing(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.SPACING_ATTR)
|
||||
|
||||
@property
|
||||
def direction(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.DIRECTION_ATTR)
|
||||
|
||||
def set_origin(self, origin: npt.NDArray[np.single]):
|
||||
"""Set the origin coordinate of the image data.
|
||||
|
||||
Args:
|
||||
origin: x, y, z coordinates to set as the origin.
|
||||
"""
|
||||
if len(origin) != self.DIMENSIONS:
|
||||
raise ValueError(f"Origin attribute must have {self.DIMENSIONS} dimensions.")
|
||||
self._set_root_attribute(self.ORIGIN_ATTR, origin)
|
||||
|
||||
def set_spacing(self, spacing: npt.NDArray[np.single]):
|
||||
"""Set the discritisation of the image data.
|
||||
|
||||
Args:
|
||||
spacing: Discritisation of the x, y, and z dimensions.
|
||||
"""
|
||||
if len(spacing) != self.DIMENSIONS:
|
||||
raise ValueError(f"Spacing attribute must have {self.DIMENSIONS} dimensions.")
|
||||
self._set_root_attribute(self.SPACING_ATTR, spacing)
|
||||
|
||||
def set_direction(self, direction: npt.NDArray[np.single]):
|
||||
"""Set the coordinate system of the image data.
|
||||
|
||||
Args:
|
||||
direction: Array of direction vectors for each dimension of
|
||||
the image data. Can be a flattened array. I.e.
|
||||
[[1, 0, 0], [0, 1, 0], [0, 0, 1]] and
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1] are equivalent.
|
||||
"""
|
||||
direction = direction.flatten()
|
||||
if len(direction) != self.DIMENSIONS * self.DIMENSIONS:
|
||||
raise ValueError(
|
||||
f"Direction array must contain {self.DIMENSIONS * self.DIMENSIONS} elements."
|
||||
)
|
||||
self._set_root_attribute(self.DIRECTION_ATTR, direction)
|
||||
|
||||
def add_point_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add point data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
points_shape = self.shape + 1
|
||||
if offset is None and any(data.shape != points_shape): # type: ignore
|
||||
raise ValueError(
|
||||
"If no offset is specified, data.shape must be one larger in each dimension than"
|
||||
f" this vtkImageData object. {data.shape} != {points_shape}"
|
||||
)
|
||||
return super().add_point_data(name, data, points_shape, offset)
|
||||
|
||||
def add_cell_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add cell data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
if offset is None and any(data.shape != self.shape): # type: ignore
|
||||
raise ValueError(
|
||||
"If no offset is specified, data.shape must match the dimensions of this"
|
||||
f" VtkImageData object. {data.shape} != {self.shape}"
|
||||
)
|
||||
return super().add_cell_data(name, data, self.shape, offset)
|
@@ -0,0 +1,172 @@
|
||||
import logging
|
||||
from os import PathLike
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from mpi4py import MPI
|
||||
|
||||
from gprMax.vtkhdf_filehandlers.vtkhdf import VtkCellType, VtkHdfFile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VtkUnstructuredGrid(VtkHdfFile):
|
||||
"""File handler for creating a VTKHDF Unstructured Grid file.
|
||||
|
||||
File format information is available here:
|
||||
https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#unstructured-grid
|
||||
"""
|
||||
|
||||
class Dataset(VtkHdfFile.Dataset):
|
||||
CONNECTIVITY = "Connectivity"
|
||||
NUMBER_OF_CELLS = "NumberOfCells"
|
||||
NUMBER_OF_CONNECTIVITY_IDS = "NumberOfConnectivityIds"
|
||||
NUMBER_OF_POINTS = "NumberOfPoints"
|
||||
OFFSETS = "Offsets"
|
||||
POINTS = "Points"
|
||||
TYPES = "Types"
|
||||
|
||||
@property
|
||||
def TYPE(self) -> Literal["UnstructuredGrid"]:
|
||||
return "UnstructuredGrid"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, PathLike],
|
||||
points: npt.NDArray,
|
||||
cell_types: npt.NDArray[VtkCellType],
|
||||
connectivity: npt.NDArray,
|
||||
cell_offsets: npt.NDArray,
|
||||
comm: Optional[MPI.Comm] = None,
|
||||
) -> None:
|
||||
"""Create a new VtkUnstructuredGrid file.
|
||||
|
||||
An unstructured grid has N points and C cells. A cell is defined
|
||||
as a collection of points which is specified by the connectivity
|
||||
and cell_offsets arguments along with the list of cell_types.
|
||||
|
||||
If the file already exists, it will be overriden. Required
|
||||
attributes (Type and Version) will be written to the file.
|
||||
|
||||
The file will be opened using the 'mpio' h5py driver if an MPI
|
||||
communicator is provided.
|
||||
|
||||
Args:
|
||||
filename: Name of the file (can be a file path). The file
|
||||
extension will be set to '.vtkhdf'.
|
||||
points: Array of point coordinates of shape (N, 3).
|
||||
cell_types: Array of VTK cell types of shape (C,).
|
||||
connectivity: Array of point IDs that together with
|
||||
cell_offsets, defines the points that make up each cell.
|
||||
Each point ID has a value between 0 and (N-1) inclusive
|
||||
and corresponds to a point in the points array.
|
||||
cell_offsets: Array listing where each cell starts and ends
|
||||
in the connectivity array. It has shape (C + 1,).
|
||||
comm (optional): MPI communicator containing all ranks that
|
||||
want to write to the file.
|
||||
|
||||
Raises:
|
||||
Value Error: Raised if argument dimensions are invalid.
|
||||
"""
|
||||
super().__init__(filename, comm)
|
||||
|
||||
if len(cell_offsets) != len(cell_types) + 1:
|
||||
raise ValueError(
|
||||
"cell_offsets should be one longer than cell_types."
|
||||
" I.e. one longer than the number of cells"
|
||||
)
|
||||
|
||||
is_sorted = lambda a: np.all(a[:-1] <= a[1:])
|
||||
if not is_sorted(cell_offsets):
|
||||
raise ValueError("cell_offsets should be sorted in ascending order")
|
||||
|
||||
if len(connectivity) < cell_offsets[-1]:
|
||||
raise ValueError("Connectivity array is shorter than final cell_offsets value")
|
||||
|
||||
elif len(connectivity) > cell_offsets[-1]:
|
||||
raise logger.warning(
|
||||
"Connectivity array longer than final cell_offsets value."
|
||||
" Some connectivity data will be ignored"
|
||||
)
|
||||
|
||||
self._write_root_dataset(self.Dataset.CONNECTIVITY, connectivity)
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_CELLS, len(cell_types))
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_CONNECTIVITY_IDS, len(connectivity))
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_POINTS, len(points))
|
||||
self._write_root_dataset(self.Dataset.OFFSETS, cell_offsets)
|
||||
self._write_root_dataset(self.Dataset.POINTS, points, xyz_data_ordering=False)
|
||||
self._write_root_dataset(self.Dataset.TYPES, cell_types)
|
||||
|
||||
@property
|
||||
def number_of_cells(self) -> int:
|
||||
number_of_cells = self._get_root_dataset(self.Dataset.NUMBER_OF_CELLS)
|
||||
return np.sum(number_of_cells, dtype=np.intc)
|
||||
|
||||
@property
|
||||
def number_of_connectivity_ids(self) -> int:
|
||||
number_of_connectivity_ids = self._get_root_dataset(self.Dataset.NUMBER_OF_CONNECTIVITY_IDS)
|
||||
return np.sum(number_of_connectivity_ids, dtype=np.intc)
|
||||
|
||||
@property
|
||||
def number_of_points(self) -> int:
|
||||
number_of_points = self._get_root_dataset(self.Dataset.NUMBER_OF_POINTS)
|
||||
return np.sum(number_of_points, dtype=np.intc)
|
||||
|
||||
def add_point_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add point data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
shape = np.array(data.shape)
|
||||
number_of_dimensions = len(shape)
|
||||
|
||||
if number_of_dimensions < 1 or number_of_dimensions > 2:
|
||||
raise ValueError(f"Data must have 1 or 2 dimensions, not {number_of_dimensions}")
|
||||
elif len(data) != self.number_of_points:
|
||||
raise ValueError(
|
||||
"Length of data must match the number of points in the vtkUnstructuredGrid."
|
||||
f" {len(data)} != {self.number_of_points}"
|
||||
)
|
||||
elif number_of_dimensions == 2 and shape[1] != 1 and shape[1] != 3:
|
||||
raise ValueError(f"The second dimension should have shape 1 or 3, not {shape[1]}")
|
||||
|
||||
return super().add_point_data(name, data, shape, offset)
|
||||
|
||||
def add_cell_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add cell data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
shape = np.array(data.shape)
|
||||
number_of_dimensions = len(shape)
|
||||
|
||||
if number_of_dimensions < 1 or number_of_dimensions > 2:
|
||||
raise ValueError(f"Data must have 1 or 2 dimensions, not {number_of_dimensions}.")
|
||||
elif len(data) != self.number_of_cells:
|
||||
raise ValueError(
|
||||
"Length of data must match the number of cells in the vtkUnstructuredGrid."
|
||||
f" {len(data)} != {self.number_of_cells}"
|
||||
)
|
||||
elif number_of_dimensions == 2 and shape[1] != 1 and shape[1] != 3:
|
||||
raise ValueError(f"The second dimension should have shape 1 or 3, not {shape[1]}")
|
||||
|
||||
return super().add_cell_data(name, data, shape, offset)
|
@@ -5,7 +5,7 @@ from enum import Enum
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import Literal, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
import h5py
|
||||
import numpy as np
|
||||
@@ -345,182 +345,6 @@ class VtkHdfFile(AbstractContextManager):
|
||||
self._write_dataset(dataset_path, data, shape, offset)
|
||||
|
||||
|
||||
class VtkImageData(VtkHdfFile):
|
||||
"""File handler for creating a VTKHDF Image Data file.
|
||||
|
||||
File format information is available here:
|
||||
https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#image-data
|
||||
"""
|
||||
|
||||
DIRECTION_ATTR = "Direction"
|
||||
ORIGIN_ATTR = "Origin"
|
||||
SPACING_ATTR = "Spacing"
|
||||
WHOLE_EXTENT_ATTR = "WholeExtent"
|
||||
|
||||
DIMENSIONS = 3
|
||||
|
||||
@property
|
||||
def TYPE(self) -> Literal["ImageData"]:
|
||||
return "ImageData"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, PathLike],
|
||||
shape: npt.NDArray[np.intc],
|
||||
origin: Optional[npt.NDArray[np.single]] = None,
|
||||
spacing: Optional[npt.NDArray[np.single]] = None,
|
||||
direction: Optional[npt.NDArray[np.single]] = None,
|
||||
comm: Optional[MPI.Comm] = None,
|
||||
):
|
||||
"""Create a new VtkImageData file.
|
||||
|
||||
If the file already exists, it will be overriden. Required
|
||||
attributes (Type and Version) will be written to the file.
|
||||
|
||||
The file will be opened using the 'mpio' h5py driver if an MPI
|
||||
communicator is provided.
|
||||
|
||||
Args:
|
||||
filename: Name of the file (can be a file path). The file
|
||||
extension will be set to '.vtkhdf'.
|
||||
shape: Shape of the image data to be stored in the file.
|
||||
This specifies the number of cells. Image data can be
|
||||
1D, 2D, or 3D.
|
||||
origin (optional): Origin of the image data. Default
|
||||
[0, 0, 0].
|
||||
spacing (optional): Discritisation of the image data.
|
||||
Default [1, 1, 1].
|
||||
direction (optional): Array of direction vectors for each
|
||||
dimension of the image data. Can be a flattened array.
|
||||
I.e. [[1, 0, 0], [0, 1, 0], [0, 0, 1]] and
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1] are equivalent. Default
|
||||
[[1, 0, 0], [0, 1, 0], [0, 0, 1]].
|
||||
comm (optional): MPI communicator containing all ranks that
|
||||
want to write to the file.
|
||||
"""
|
||||
super().__init__(filename, comm)
|
||||
|
||||
if len(shape) == 0:
|
||||
raise ValueError(f"Shape must not be empty.")
|
||||
if len(shape) > self.DIMENSIONS:
|
||||
raise ValueError(f"Shape must not have more than {self.DIMENSIONS} dimensions.")
|
||||
elif len(shape) < self.DIMENSIONS:
|
||||
shape = np.concatenate((shape, np.ones(self.DIMENSIONS - len(shape), dtype=np.intc)))
|
||||
|
||||
self.shape = shape
|
||||
|
||||
whole_extent = np.zeros(2 * self.DIMENSIONS, dtype=np.intc)
|
||||
whole_extent[1::2] = self.shape
|
||||
self._set_root_attribute(self.WHOLE_EXTENT_ATTR, whole_extent)
|
||||
|
||||
if origin is None:
|
||||
origin = np.zeros(self.DIMENSIONS, dtype=np.single)
|
||||
self.set_origin(origin)
|
||||
|
||||
if spacing is None:
|
||||
spacing = np.ones(self.DIMENSIONS, dtype=np.single)
|
||||
self.set_spacing(spacing)
|
||||
|
||||
if direction is None:
|
||||
direction = np.diag(np.ones(self.DIMENSIONS, dtype=np.single))
|
||||
self.set_direction(direction)
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> npt.NDArray[np.intc]:
|
||||
return self._get_root_attribute(self.WHOLE_EXTENT_ATTR)
|
||||
|
||||
@property
|
||||
def origin(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.ORIGIN_ATTR)
|
||||
|
||||
@property
|
||||
def spacing(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.SPACING_ATTR)
|
||||
|
||||
@property
|
||||
def direction(self) -> npt.NDArray[np.single]:
|
||||
return self._get_root_attribute(self.DIRECTION_ATTR)
|
||||
|
||||
def set_origin(self, origin: npt.NDArray[np.single]):
|
||||
"""Set the origin coordinate of the image data.
|
||||
|
||||
Args:
|
||||
origin: x, y, z coordinates to set as the origin.
|
||||
"""
|
||||
if len(origin) != self.DIMENSIONS:
|
||||
raise ValueError(f"Origin attribute must have {self.DIMENSIONS} dimensions.")
|
||||
self._set_root_attribute(self.ORIGIN_ATTR, origin)
|
||||
|
||||
def set_spacing(self, spacing: npt.NDArray[np.single]):
|
||||
"""Set the discritisation of the image data.
|
||||
|
||||
Args:
|
||||
spacing: Discritisation of the x, y, and z dimensions.
|
||||
"""
|
||||
if len(spacing) != self.DIMENSIONS:
|
||||
raise ValueError(f"Spacing attribute must have {self.DIMENSIONS} dimensions.")
|
||||
self._set_root_attribute(self.SPACING_ATTR, spacing)
|
||||
|
||||
def set_direction(self, direction: npt.NDArray[np.single]):
|
||||
"""Set the coordinate system of the image data.
|
||||
|
||||
Args:
|
||||
direction: Array of direction vectors for each dimension of
|
||||
the image data. Can be a flattened array. I.e.
|
||||
[[1, 0, 0], [0, 1, 0], [0, 0, 1]] and
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1] are equivalent.
|
||||
"""
|
||||
direction = direction.flatten()
|
||||
if len(direction) != self.DIMENSIONS * self.DIMENSIONS:
|
||||
raise ValueError(
|
||||
f"Direction array must contain {self.DIMENSIONS * self.DIMENSIONS} elements."
|
||||
)
|
||||
self._set_root_attribute(self.DIRECTION_ATTR, direction)
|
||||
|
||||
def add_point_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add point data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
points_shape = self.shape + 1
|
||||
if offset is None and any(data.shape != points_shape): # type: ignore
|
||||
raise ValueError(
|
||||
"If no offset is specified, data.shape must be one larger in each dimension than"
|
||||
f" this vtkImageData object. {data.shape} != {points_shape}"
|
||||
)
|
||||
return super().add_point_data(name, data, points_shape, offset)
|
||||
|
||||
def add_cell_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add cell data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
if offset is None and any(data.shape != self.shape): # type: ignore
|
||||
raise ValueError(
|
||||
"If no offset is specified, data.shape must match the dimensions of this"
|
||||
f" VtkImageData object. {data.shape} != {self.shape}"
|
||||
)
|
||||
return super().add_cell_data(name, data, self.shape, offset)
|
||||
|
||||
|
||||
class VtkCellType(np.uint8, Enum):
|
||||
"""VTK cell types as defined here:
|
||||
https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html#l00019
|
||||
@@ -544,164 +368,3 @@ class VtkCellType(np.uint8, Enum):
|
||||
PYRAMID = 14
|
||||
PENTAGONAL_PRISM = 15
|
||||
HEXAGONAL_PRISM = 16
|
||||
|
||||
|
||||
class VtkUnstructuredGrid(VtkHdfFile):
|
||||
"""File handler for creating a VTKHDF Unstructured Grid file.
|
||||
|
||||
File format information is available here:
|
||||
https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#unstructured-grid
|
||||
"""
|
||||
|
||||
class Dataset(VtkHdfFile.Dataset):
|
||||
CONNECTIVITY = "Connectivity"
|
||||
NUMBER_OF_CELLS = "NumberOfCells"
|
||||
NUMBER_OF_CONNECTIVITY_IDS = "NumberOfConnectivityIds"
|
||||
NUMBER_OF_POINTS = "NumberOfPoints"
|
||||
OFFSETS = "Offsets"
|
||||
POINTS = "Points"
|
||||
TYPES = "Types"
|
||||
|
||||
@property
|
||||
def TYPE(self) -> Literal["UnstructuredGrid"]:
|
||||
return "UnstructuredGrid"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, PathLike],
|
||||
points: npt.NDArray,
|
||||
cell_types: npt.NDArray[VtkCellType],
|
||||
connectivity: npt.NDArray,
|
||||
cell_offsets: npt.NDArray,
|
||||
comm: Optional[MPI.Comm] = None,
|
||||
) -> None:
|
||||
"""Create a new VtkUnstructuredGrid file.
|
||||
|
||||
An unstructured grid has N points and C cells. A cell is defined
|
||||
as a collection of points which is specified by the connectivity
|
||||
and cell_offsets arguments along with the list of cell_types.
|
||||
|
||||
If the file already exists, it will be overriden. Required
|
||||
attributes (Type and Version) will be written to the file.
|
||||
|
||||
The file will be opened using the 'mpio' h5py driver if an MPI
|
||||
communicator is provided.
|
||||
|
||||
Args:
|
||||
filename: Name of the file (can be a file path). The file
|
||||
extension will be set to '.vtkhdf'.
|
||||
points: Array of point coordinates of shape (N, 3).
|
||||
cell_types: Array of VTK cell types of shape (C,).
|
||||
connectivity: Array of point IDs that together with
|
||||
cell_offsets, defines the points that make up each cell.
|
||||
Each point ID has a value between 0 and (N-1) inclusive
|
||||
and corresponds to a point in the points array.
|
||||
cell_offsets: Array listing where each cell starts and ends
|
||||
in the connectivity array. It has shape (C + 1,).
|
||||
comm (optional): MPI communicator containing all ranks that
|
||||
want to write to the file.
|
||||
|
||||
Raises:
|
||||
Value Error: Raised if argument dimensions are invalid.
|
||||
"""
|
||||
super().__init__(filename, comm)
|
||||
|
||||
if len(cell_offsets) != len(cell_types) + 1:
|
||||
raise ValueError(
|
||||
"cell_offsets should be one longer than cell_types."
|
||||
" I.e. one longer than the number of cells"
|
||||
)
|
||||
|
||||
is_sorted = lambda a: np.all(a[:-1] <= a[1:])
|
||||
if not is_sorted(cell_offsets):
|
||||
raise ValueError("cell_offsets should be sorted in ascending order")
|
||||
|
||||
if len(connectivity) < cell_offsets[-1]:
|
||||
raise ValueError("Connectivity array is shorter than final cell_offsets value")
|
||||
|
||||
elif len(connectivity) > cell_offsets[-1]:
|
||||
raise logger.warning(
|
||||
"Connectivity array longer than final cell_offsets value."
|
||||
" Some connectivity data will be ignored"
|
||||
)
|
||||
|
||||
self._write_root_dataset(self.Dataset.CONNECTIVITY, connectivity)
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_CELLS, len(cell_types))
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_CONNECTIVITY_IDS, len(connectivity))
|
||||
self._write_root_dataset(self.Dataset.NUMBER_OF_POINTS, len(points))
|
||||
self._write_root_dataset(self.Dataset.OFFSETS, cell_offsets)
|
||||
self._write_root_dataset(self.Dataset.POINTS, points, xyz_data_ordering=False)
|
||||
self._write_root_dataset(self.Dataset.TYPES, cell_types)
|
||||
|
||||
@property
|
||||
def number_of_cells(self) -> int:
|
||||
number_of_cells = self._get_root_dataset(self.Dataset.NUMBER_OF_CELLS)
|
||||
return np.sum(number_of_cells, dtype=np.intc)
|
||||
|
||||
@property
|
||||
def number_of_connectivity_ids(self) -> int:
|
||||
number_of_connectivity_ids = self._get_root_dataset(self.Dataset.NUMBER_OF_CONNECTIVITY_IDS)
|
||||
return np.sum(number_of_connectivity_ids, dtype=np.intc)
|
||||
|
||||
@property
|
||||
def number_of_points(self) -> int:
|
||||
number_of_points = self._get_root_dataset(self.Dataset.NUMBER_OF_POINTS)
|
||||
return np.sum(number_of_points, dtype=np.intc)
|
||||
|
||||
def add_point_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add point data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
shape = np.array(data.shape)
|
||||
number_of_dimensions = len(shape)
|
||||
|
||||
if number_of_dimensions < 1 or number_of_dimensions > 2:
|
||||
raise ValueError(f"Data must have 1 or 2 dimensions, not {number_of_dimensions}")
|
||||
elif len(data) != self.number_of_points:
|
||||
raise ValueError(
|
||||
"Length of data must match the number of points in the vtkUnstructuredGrid."
|
||||
f" {len(data)} != {self.number_of_points}"
|
||||
)
|
||||
elif number_of_dimensions == 2 and shape[1] != 1 and shape[1] != 3:
|
||||
raise ValueError(f"The second dimension should have shape 1 or 3, not {shape[1]}")
|
||||
|
||||
return super().add_point_data(name, data, shape, offset)
|
||||
|
||||
def add_cell_data(
|
||||
self, name: str, data: npt.NDArray, offset: Optional[npt.NDArray[np.intc]] = None
|
||||
):
|
||||
"""Add cell data to the VTKHDF file.
|
||||
|
||||
Args:
|
||||
name: Name of the dataset.
|
||||
data: Data to be saved.
|
||||
offset (optional): Offset to store the provided data at. Can
|
||||
be omitted if data provides the full dataset.
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if data has invalid dimensions.
|
||||
"""
|
||||
shape = np.array(data.shape)
|
||||
number_of_dimensions = len(shape)
|
||||
|
||||
if number_of_dimensions < 1 or number_of_dimensions > 2:
|
||||
raise ValueError(f"Data must have 1 or 2 dimensions, not {number_of_dimensions}.")
|
||||
elif len(data) != self.number_of_cells:
|
||||
raise ValueError(
|
||||
"Length of data must match the number of cells in the vtkUnstructuredGrid."
|
||||
f" {len(data)} != {self.number_of_cells}"
|
||||
)
|
||||
elif number_of_dimensions == 2 and shape[1] != 1 and shape[1] != 3:
|
||||
raise ValueError(f"The second dimension should have shape 1 or 3, not {shape[1]}")
|
||||
|
||||
return super().add_cell_data(name, data, shape, offset)
|
||||
|
在新工单中引用
屏蔽一个用户