Source code for geoh5py.data.data

#  Copyright (c) 2024 Mira Geoscience Ltd.
#
#  This file is part of geoh5py.
#
#  geoh5py is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  geoh5py 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 Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with geoh5py.  If not, see <https://www.gnu.org/licenses/>.

from __future__ import annotations

from abc import abstractmethod

import numpy as np

from ..shared import Entity
from ..shared.utils import mask_by_extent
from .data_association_enum import DataAssociationEnum
from .data_type import DataType
from .primitive_type_enum import PrimitiveTypeEnum


[docs] class Data(Entity): """ Base class for Data entities. """ _attribute_map = Entity._attribute_map.copy() _attribute_map.update({"Association": "association", "Modifiable": "modifiable"}) _visible = False def __init__( self, data_type: DataType, association: DataAssociationEnum = DataAssociationEnum.OBJECT, **kwargs, ): self.association = association self._on_file = False self._modifiable = True if ( not isinstance(data_type, DataType) or data_type.primitive_type != self.primitive_type() ): raise TypeError( "Input 'data_type' must be a DataType object of primitive_type 'TEXT'." ) self.entity_type = data_type self._values = None super().__init__(**kwargs) if self.entity_type.name == "Entity": self.entity_type.name = self.name
[docs] def copy( self, parent=None, clear_cache: bool = False, mask: np.ndarray | None = None, **kwargs, ) -> Data: """ Function to copy data to a different parent entity. :param parent: Target parent to copy the entity under. Copied to current :obj:`~geoh5py.shared.entity.Entity.parent` if None. :param clear_cache: Clear array attributes after copy. :param mask: Array of bool defining the values to keep. :param kwargs: Additional keyword arguments to pass to the copy constructor. :return entity: Registered Entity to the workspace. """ if parent is None: parent = self.parent if self.values is not None and mask is not None: if not isinstance(mask, np.ndarray): raise TypeError("Mask must be an array or None.") if mask.dtype != bool or mask.shape != self.values.shape: raise ValueError( f"Mask must be a boolean array of shape {self.values.shape}, not {mask.shape}" ) n_values = ( parent.n_cells if self.association is DataAssociationEnum.CELL else parent.n_vertices ) if n_values < self.values.shape[0]: kwargs.update({"values": self.values[mask]}) else: values = np.ones_like(self.values) * self.nan_value values[mask] = self.values[mask] kwargs.update({"values": values}) new_entity = parent.workspace.copy_to_parent( self, parent, clear_cache=clear_cache, **kwargs, ) return new_entity
[docs] def copy_from_extent( self, extent: np.ndarray, parent=None, clear_cache: bool = False, inverse: bool = False, **kwargs, ) -> Entity | None: """ Function to copy data based on a bounding box extent. :param extent: Bounding box extent requested for the input entity, as supplied for :func:`~geoh5py.shared.entity.Entity.mask_by_extent`. :param parent: Target parent to copy the entity under. Copied to current :obj:`~geoh5py.shared.entity.Entity.parent` if None. :param clear_cache: Clear array attributes after copy. :param inverse: Keep the inverse (clip) of the extent selection. :param kwargs: Additional keyword arguments to pass to the copy constructor. :return entity: Registered Entity to the workspace. """ indices = self.mask_by_extent(extent, inverse=inverse) if indices is None: return None return self.copy( parent=parent, clear_cache=clear_cache, mask=indices, **kwargs, )
@property def n_values(self) -> int | None: """ :obj:`int`: Number of expected data values based on :obj:`~geoh5py.data.data.Data.association` """ if self.association is DataAssociationEnum.VERTEX: return self.parent.n_vertices if self.association is DataAssociationEnum.DEPTH: return self.parent.n_vertices if self.association is DataAssociationEnum.CELL: return self.parent.n_cells if self.association is DataAssociationEnum.FACE: return self.parent.n_faces if self.association is DataAssociationEnum.OBJECT: return 1 return None @property def nan_value(self) -> None: """ Value used to represent missing data in python. """ return None @property def values(self): """ Data values """ return self._values @property def association(self) -> DataAssociationEnum: """ :obj:`~geoh5py.data.data_association_enum.DataAssociationEnum`: Relationship made between the :func:`~geoh5py.data.data.Data.values` and elements of the :obj:`~geoh5py.shared.entity.Entity.parent` object. Association can be set from a :obj:`str` chosen from the list of available :obj:`~geoh5py.data.data_association_enum.DataAssociationEnum` options. """ return self._association @association.setter def association(self, value: str | DataAssociationEnum): if isinstance(value, str): if value.upper() not in DataAssociationEnum.__members__: raise ValueError( f"Association flag should be one of {DataAssociationEnum.__members__}" ) value = getattr(DataAssociationEnum, value.upper()) if not isinstance(value, DataAssociationEnum): raise TypeError(f"Association must be of type {DataAssociationEnum}") self._association = value @property def modifiable(self) -> bool: """ :obj:`bool` Entity can be modified. """ return self._modifiable @modifiable.setter def modifiable(self, value: bool): self._modifiable = value self.workspace.update_attribute(self, "attributes") @property def entity_type(self) -> DataType: """ :obj:`~geoh5py.data.data_type.DataType` """ return self._entity_type @entity_type.setter def entity_type(self, data_type: DataType): self._entity_type = data_type self.workspace.update_attribute(self, "entity_type")
[docs] @classmethod @abstractmethod def primitive_type(cls) -> PrimitiveTypeEnum: """Abstract method to return the primitive type of the class."""
[docs] def mask_by_extent( self, extent: np.ndarray, inverse: bool = False ) -> np.ndarray | None: """ Sub-class extension of :func:`~geoh5py.shared.entity.Entity.mask_by_extent`. Uses the parent object's vertices or centroids coordinates. """ if self.association is DataAssociationEnum.VERTEX: return mask_by_extent(self.parent.vertices, extent, inverse=inverse) if self.association is DataAssociationEnum.CELL: if getattr(self.parent, "centroids", None) is not None: return mask_by_extent(self.parent.centroids, extent, inverse=inverse) indices = mask_by_extent(self.parent.vertices, extent, inverse=inverse) if indices is not None: indices = np.all(indices[self.parent.cells], axis=1) return indices return None
def __call__(self): return self.values