Skip to content

Commit

Permalink
Merge branch 'release/0.10.0' into GEOPY-1739
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthieuCMira authored Oct 10, 2024
2 parents 9c94b13 + a397f73 commit 3dcd70f
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 14 deletions.
92 changes: 90 additions & 2 deletions geoh5py/data/data_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@
from .reference_value_map import BOOLEAN_VALUE_MAP, ReferenceValueMap


if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from ..objects import ObjectBase
from ..workspace import Workspace
from .data import Data
from .referenced_data import ReferencedData


ColorMapping = Literal[
"linear",
"equal_area",
Expand All @@ -57,10 +56,14 @@ class DataType(EntityType):
:param workspace: An active Workspace.
:param primitive_type: The primitive type of the data.
:param color_map: The colormap used for plotting.
:param duplicate_on_copy: Force a copy on copy of the data entity.
:param duplicate_type_on_copy: Force a copy on copy of the data entity.
:param hidden: If the data are hidden or not.
:param mapping: The type of color stretching to plot the colormap.
:param number_of_bins: The number of bins used by the histogram.
:param precision: The decimals precision of the data to display.
:param scale: The type of scale of the data.
:param scientific_notation: If the data should be displayed in scientific notation.
:param transparent_no_data: If the no data values are displayed as transparent or not.
:param units: The type of the units of the data.
:param kwargs: Additional keyword arguments to set as attributes
Expand All @@ -73,8 +76,11 @@ class DataType(EntityType):
"Hidden": "hidden",
"Mapping": "mapping",
"Number of bins": "number_of_bins",
"Precision": "precision",
"Primitive type": "primitive_type",
"Transparent no data": "transparent_no_data",
"Scale": "scale",
"Scientific notation": "scientific_notation",
}
)

Expand All @@ -85,21 +91,30 @@ def __init__(
type[Data] | PrimitiveTypeEnum | str
) = PrimitiveTypeEnum.INVALID,
color_map: ColorMap | None = None,
duplicate_on_copy: bool = False,
duplicate_type_on_copy: bool = False,
hidden: bool = False,
mapping: ColorMapping = "equal_area",
number_of_bins: int | None = None,
precision: int = 2,
scale: str | None = None,
scientific_notation: bool = False,
transparent_no_data: bool = True,
units: str | None = None,
**kwargs,
):
super().__init__(workspace, **kwargs)

self.color_map = color_map
self.duplicate_on_copy = duplicate_on_copy
self.duplicate_type_on_copy = duplicate_type_on_copy
self.hidden = hidden
self.mapping = mapping
self.number_of_bins = number_of_bins
self.precision = precision
self.primitive_type = self.validate_primitive_type(primitive_type)
self.scale = scale
self.scientific_notation = scientific_notation
self.transparent_no_data = transparent_no_data
self.units = units

Expand Down Expand Up @@ -145,6 +160,24 @@ def color_map(self, color_map: ColorMap | dict | np.ndarray | None):

self.workspace.update_attribute(self, "color_map")

@property
def duplicate_on_copy(self) -> bool:
"""
If the data type should be duplicated on copy.
"""
return self._duplicate_on_copy

@duplicate_on_copy.setter
def duplicate_on_copy(self, value: bool):
if not isinstance(value, bool) and value not in [1, 0]:
raise TypeError(
f"Attribute 'duplicate_on_copy' must be a bool, not {type(value)}"
)

self._duplicate_on_copy = bool(value)
if self.on_file:
self.workspace.update_attribute(self, "attributes")

@property
def duplicate_type_on_copy(self) -> bool:
"""
Expand Down Expand Up @@ -271,6 +304,28 @@ def number_of_bins(self, n_bins: int | None):

self.workspace.update_attribute(self, "attributes")

@property
def precision(self) -> int:
"""
The decimals precision of the data to display.
"""
return self._precision

@precision.setter
def precision(self, value: int):
if (
not isinstance(value, (int, float, np.integer, np.floating))
or (isinstance(value, (float, np.floating)) and not value.is_integer())
or value < 0
):
raise TypeError(
f"Attribute 'precision' must be an integer greater than 0, not {value}"
)

self._precision = int(value)

self.workspace.update_attribute(self, "attributes")

@property
def primitive_type(self) -> PrimitiveTypeEnum:
"""
Expand Down Expand Up @@ -351,6 +406,39 @@ def primitive_type_from_values(values: np.ndarray | None) -> PrimitiveTypeEnum:
)
return primitive_type

@property
def scale(self) -> str | None:
"""
The type of scale of the data.
"""
return self._scale

@scale.setter
def scale(self, value: str | None):
if value not in ["Linear", "Log", None]:
raise ValueError(
f"Attribute 'scale' must be one of 'Linear', 'Log', NoneType, not {value}"
)
self._scale = value
self.workspace.update_attribute(self, "attributes")

@property
def scientific_notation(self) -> bool:
"""
If the data should be displayed in scientific notation.
"""
return self._scientific_notation

@scientific_notation.setter
def scientific_notation(self, value: bool):
if value not in [True, False, 1, 0]:
raise TypeError(
f"Attribute 'scientific_notation' must be a bool, not {type(value)}"
)

self._scientific_notation = bool(value)
self.workspace.update_attribute(self, "attributes")

@staticmethod
def validate_primitive_type(
primitive_type: PrimitiveTypeEnum | str | type[Data],
Expand Down
5 changes: 3 additions & 2 deletions geoh5py/objects/block_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ def local_axis_centers(self, axis: str) -> np.ndarray:
:param axis: Axis to get the centers for.
"""
n_cells = getattr(self, f"{axis}_cells")
return np.cumsum(n_cells) - n_cells / 2.0
out = np.cumsum(getattr(self, f"{axis}_cell_delimiters"))
out[2:] = out[2:] - out[:-2]
return out[1:] / 2

@property
def centroids(self) -> np.ndarray:
Expand Down
6 changes: 4 additions & 2 deletions geoh5py/shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

from __future__ import annotations

import warnings
from abc import ABC
from collections.abc import Callable
from contextlib import contextmanager
Expand All @@ -26,6 +25,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any
from uuid import UUID
from warnings import warn

import h5py
import numpy as np
Expand Down Expand Up @@ -93,6 +93,7 @@
"Octree Cells": "octree_cells",
"Partially hidden": "partially_hidden",
"Planning": "planning",
"Precision": "precision",
"Primitive type": "primitive_type",
"Prisms": "prisms",
"Properties": "properties",
Expand All @@ -103,6 +104,7 @@
"Referenced": "REFERENCED",
"Rotation": "rotation",
"Scale": "scale",
"Scientific notation": "scientific_notation",
"Surveys": "surveys",
"Text": "TEXT",
"Trace": "trace",
Expand Down Expand Up @@ -170,7 +172,7 @@ def fetch_active_workspace(workspace: Workspace | None, mode: str = "r"):
pass
else:
if geoh5 is not None:
warnings.warn(
warn(
f"Closing the workspace in mode '{workspace.geoh5.mode}' "
f"and re-opening in mode '{mode}'."
)
Expand Down
7 changes: 2 additions & 5 deletions geoh5py/ui_json/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@

from __future__ import annotations

import shutil
import warnings
from io import BytesIO
from pathlib import Path
from shutil import copy, move
from time import time
from typing import Any

Expand Down Expand Up @@ -326,9 +326,6 @@ def monitored_directory_copy(
with Workspace.create(working_path / temp_geoh5) as w_s:
entity.copy(parent=w_s, copy_children=copy_children)

shutil.move(
working_path / temp_geoh5,
directory_path / temp_geoh5,
)
move(working_path / temp_geoh5, directory_path / temp_geoh5, copy)

return str(directory_path / temp_geoh5)
6 changes: 3 additions & 3 deletions geoh5py/workspace/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from __future__ import annotations

import inspect
import shutil
import subprocess
import tempfile
import uuid
Expand All @@ -31,6 +30,7 @@
from getpass import getuser
from io import BytesIO
from pathlib import Path
from shutil import copy, move
from subprocess import CalledProcessError
from typing import Any, ClassVar, cast
from weakref import ReferenceType
Expand Down Expand Up @@ -212,7 +212,7 @@ def close(self):
stdout=subprocess.DEVNULL,
)
Path(self._h5file).unlink()
shutil.move(temp_file, self._h5file)
move(temp_file, self._h5file, copy)
except CalledProcessError:
pass

Expand Down Expand Up @@ -1331,7 +1331,7 @@ def save_as(self, filepath: str | Path) -> Workspace:
elif self.h5file is None:
raise ValueError("Input 'h5file' file must be specified.")
else:
shutil.copy(self.h5file, filepath)
move(self.h5file, filepath, copy)

self._h5file = filepath

Expand Down
24 changes: 24 additions & 0 deletions tests/block_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@
from geoh5py.workspace import Workspace


def test_negative_cell_delimiters_centroids(tmp_path):
workspace = Workspace.create(tmp_path / "test.geoh5")
block_model = BlockModel.create(
workspace,
name="test",
u_cell_delimiters=np.array([-1, 0, 1]),
v_cell_delimiters=np.array([-1, 0, 1]),
z_cell_delimiters=np.array([-3, -2, -1]),
origin=np.r_[0, 0, 0],
)
assert np.allclose(
block_model.centroids[:, 0],
np.array([-0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5]),
)
assert np.allclose(
block_model.centroids[:, 1],
np.array([-0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5]),
)
assert np.allclose(
block_model.centroids[:, 2],
np.array([-2.5, -1.5, -2.5, -1.5, -2.5, -1.5, -2.5, -1.5]),
)


def test_create_block_model_data(tmp_path):
name = "MyTestBlockModel"
h5file_path = tmp_path / r"block_model.geoh5"
Expand Down
37 changes: 37 additions & 0 deletions tests/data_instantiation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,31 @@ def _can_find(workspace, created_data):
assert workspace.find_data(created_data.uid) is created_data


def test_scale_continuity(tmp_path):
h5file_path = tmp_path / r"testPoints.geoh5"
with Workspace.create(h5file_path) as workspace:
points = Points.create(
workspace,
name="point",
vertices=np.vstack((np.arange(20), np.arange(20), np.zeros(20))).T,
allow_move=False,
)
data = points.add_data(
{"DataValues": {"association": "VERTEX", "values": np.random.randn(20)}}
)
data.entity_type.scale = "Log"
data.entity_type.precision = 4.0
data.entity_type.scientific_notation = True

with Workspace(h5file_path) as workspace:
points = workspace.get_entity("point")[0]
data = points.get_data("DataValues")[0]

assert data.entity_type.scale == "Log"
assert data.entity_type.precision == 4
assert data.entity_type.scientific_notation is True


def test_copy_from_extent(tmp_path):
# Generate a random cloud of points
h5file_path = tmp_path / r"testPoints.geoh5"
Expand Down Expand Up @@ -162,3 +187,15 @@ def test_data_type_attributes():

with pytest.raises(ValueError, match=r"Attribute 'primitive_type' should be one"):
data_type.validate_primitive_type(1)

with pytest.raises(TypeError, match=r"Attribute 'duplicate_on_copy'"):
data_type.duplicate_on_copy = "bidon"

with pytest.raises(TypeError, match=r"Attribute 'precision'"):
data_type.precision = "bidon"

with pytest.raises(ValueError, match=r"Attribute 'scale'"):
data_type.scale = "bidon"

with pytest.raises(TypeError, match=r"Attribute 'scientific_notation'"):
data_type.scientific_notation = "bidon"

0 comments on commit 3dcd70f

Please sign in to comment.