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,
**kwargs,
):
self._association = None
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
data_type.workspace._register_data(self)
[docs]
def copy(
self,
parent=None,
copy_children: bool = True,
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 copy_children: (Optional) Create copies of all children entities along with it.
: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
@property
def extent(self) -> np.ndarray | None:
"""
Geography bounding box of the parent object.
:return: shape(2, 3) Bounding box defined by the bottom South-West and
top North-East coordinates.
"""
return None
@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 | None:
"""
: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:
...
[docs]
def add_file(self, file: str):
"""
Alias not implemented from base Entity class.
"""
raise NotImplementedError("Data entity cannot contain files.")
[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