From 237c3bae4a8269627404ea23a191998cf135384c Mon Sep 17 00:00:00 2001 From: nmannall Date: Fri, 8 Nov 2024 13:03:09 +0000 Subject: [PATCH] Split up VTKHDF classes into seperate files --- gprMax/geometry_outputs.py | 4 +- gprMax/vtkhdf_filehandlers/vtk_image_data.py | 184 ++++++++++ .../vtk_unstructured_grid.py | 172 +++++++++ gprMax/vtkhdf_filehandlers/vtkhdf.py | 339 +----------------- 4 files changed, 360 insertions(+), 339 deletions(-) create mode 100644 gprMax/vtkhdf_filehandlers/vtk_image_data.py create mode 100644 gprMax/vtkhdf_filehandlers/vtk_unstructured_grid.py diff --git a/gprMax/geometry_outputs.py b/gprMax/geometry_outputs.py index 1adfbe8a..c08e3c47 100644 --- a/gprMax/geometry_outputs.py +++ b/gprMax/geometry_outputs.py @@ -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 diff --git a/gprMax/vtkhdf_filehandlers/vtk_image_data.py b/gprMax/vtkhdf_filehandlers/vtk_image_data.py new file mode 100644 index 00000000..e521d201 --- /dev/null +++ b/gprMax/vtkhdf_filehandlers/vtk_image_data.py @@ -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) diff --git a/gprMax/vtkhdf_filehandlers/vtk_unstructured_grid.py b/gprMax/vtkhdf_filehandlers/vtk_unstructured_grid.py new file mode 100644 index 00000000..0f9a5c0f --- /dev/null +++ b/gprMax/vtkhdf_filehandlers/vtk_unstructured_grid.py @@ -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) diff --git a/gprMax/vtkhdf_filehandlers/vtkhdf.py b/gprMax/vtkhdf_filehandlers/vtkhdf.py index 36231b20..a444dcc7 100644 --- a/gprMax/vtkhdf_filehandlers/vtkhdf.py +++ b/gprMax/vtkhdf_filehandlers/vtkhdf.py @@ -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)