Source code for geoh5py.groups.property_group_table
# 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 ABC
from typing import TYPE_CHECKING
from uuid import UUID
import numpy as np
from ..data import DataAssociationEnum, ReferencedData
from ..data.primitive_type_enum import DataTypeEnum
from ..shared.utils import decode_byte_array
if TYPE_CHECKING: # pragma: no cover
from .property_group import PropertyGroup
[docs]
class PropertyGroupTable(ABC):
"""
A class to store the information of a property group.
:param property_group: The property group to extract the data from.
"""
locations_keys = ("X", "Y", "Z")
def __init__(self, property_group: PropertyGroup):
if not hasattr(property_group, "property_group_type"):
raise TypeError("'property_group' must be a PropertyGroup object.")
if hasattr(property_group.parent, "collar"):
raise NotImplementedError(
"PropertyGroupTable is not supported for Drillhole objects."
)
self._property_group: PropertyGroup = property_group
def __call__(
self, spatial_index: bool = False, use_uids: bool = False, mapped: bool = False
) -> np.ndarray | None:
"""
Create a structured array with the data of the properties.
:param spatial_index: If True, the spatial index is added to the table.
:param use_uids: If True, the uids are used as columns name.
:param mapped: If True, the ReferencedData are mapped.
:return: A table with the data of the properties.
"""
if (
self.property_group.properties is None
or self.property_group.properties_name is None
):
return None
keys: list[UUID] = self.property_group.properties
names: list[str] | list[UUID] = (
self.property_group.properties
if use_uids
else self.property_group.properties_name
)
output_array = self._create_empty_structured_array(
names, keys, spatial_index, mapped
)
if spatial_index:
for idx, key in enumerate(self.locations_keys):
output_array[key] = self.locations[:, idx]
for key, name in zip(keys, names, strict=False): # type: ignore
data = self.property_group.parent.get_data(key)[0]
if isinstance(data, ReferencedData) and mapped:
output_array[str(name)] = decode_byte_array(data.mapped_values, str)
else:
output_array[str(name)] = data.values
return output_array
def _create_empty_structured_array(
self,
properties_name: list[str] | list[UUID],
properties_keys: list[UUID],
spatial_index: bool = False,
mapped: bool = False,
) -> np.ndarray:
"""
Create an empty structured array that can contains the data.
:param properties_name: The names of the properties.
:param properties_keys: The keys of the properties.
:param spatial_index: If True, the association is added to the table.
:param mapped: If True, the ReferencedData are mapped.
:return: an empty structured array.
"""
dtypes = (
[(loc, np.float32) for loc in self.locations_keys] if spatial_index else []
)
for key, name in zip(properties_keys, properties_name, strict=False):
data = self.property_group.parent.get_data(key)[0]
dtype: type | str = DataTypeEnum.from_primitive_type(
data.entity_type.primitive_type
)
if (dtype not in [np.float32, np.int32, np.uint32, bool]) or (
isinstance(data, ReferencedData) and mapped
):
dtype = "O"
dtypes.append((str(name), dtype))
empty_array = np.recarray((self.size,), dtype=dtypes)
return empty_array
@property
def locations(self) -> np.ndarray:
"""
The locations of the association table.
This function is needed as a data can be both associated to cell or
vertex in CellObjects.
"""
if self.property_group.association == DataAssociationEnum.VERTEX:
return self.property_group.parent.vertices # type: ignore
if self.property_group.association == DataAssociationEnum.CELL:
return self.property_group.parent.centroids # type: ignore
raise ValueError(
f"The association {self.property_group.association} is not supported. "
f"Only VERTEX and CELL associations are supported."
)
@property
def property_group(self) -> PropertyGroup:
"""
The property group to extract the data from.
"""
return self._property_group
@property
def size(self) -> int:
"""
The size of the properties in the group.
"""
return self.locations.shape[0]