evo-objects 0.6.4__tar.gz → 0.6.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evo_objects-0.6.4 → evo_objects-0.6.6}/PKG-INFO +2 -2
- {evo_objects-0.6.4 → evo_objects-0.6.6}/pyproject.toml +2 -2
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/exceptions.py +25 -1
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_grid.py +25 -7
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/base.py +29 -9
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/block_model_ref.py +8 -4
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/regular_grid.py +1 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/regular_masked_grid.py +1 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/tensor_grid.py +1 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/types.py +50 -9
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/variogram.py +12 -6
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/data.py +29 -25
- {evo_objects-0.6.4 → evo_objects-0.6.6}/.gitignore +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/LICENSE.md +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/_model_config.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/client/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/client/api_client.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/client/object_client.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/client/parse.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/data.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/api/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/api/data_api.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/api/metadata_api.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/api/objects_api.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/api/stages_api.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/endpoints/models.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/io.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/parquet/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/parquet/loader.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/py.typed +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_data.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_downhole.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_model.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_prefetch.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/_utils.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/attributes.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/downhole_collection.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/downhole_intervals.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/exceptions.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/pointset.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/typed/spatial.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/__init__.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/downhole.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/table_formats.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/tables.py +0 -0
- {evo_objects-0.6.4 → evo_objects-0.6.6}/src/evo/objects/utils/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: evo-objects
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
4
4
|
Summary: Python SDK for using the Seequent Evo Geoscience Object API
|
|
5
5
|
Project-URL: Source, https://github.com/SeequentEvo/evo-python-sdk
|
|
6
6
|
Project-URL: Tracker, https://github.com/SeequentEvo/evo-python-sdk/issues
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://developer.seequent.com/
|
|
|
9
9
|
Author-email: Seequent <support@seequent.com>
|
|
10
10
|
License-File: LICENSE.md
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
|
-
Requires-Dist: evo-sdk-common[jmespath]>=0.5.
|
|
12
|
+
Requires-Dist: evo-sdk-common[jmespath]>=0.5.31
|
|
13
13
|
Requires-Dist: pydantic<3,>=2
|
|
14
14
|
Provides-Extra: aiohttp
|
|
15
15
|
Requires-Dist: evo-sdk-common[aiohttp]; extra == 'aiohttp'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "evo-objects"
|
|
3
3
|
description = "Python SDK for using the Seequent Evo Geoscience Object API"
|
|
4
|
-
version = "0.6.
|
|
4
|
+
version = "0.6.6"
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
license-files = ["LICENSE.md"]
|
|
7
7
|
dynamic = ["readme"]
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
dependencies = [
|
|
13
|
-
"evo-sdk-common[jmespath]>=0.5.
|
|
13
|
+
"evo-sdk-common[jmespath]>=0.5.31",
|
|
14
14
|
"pydantic>=2,<3",
|
|
15
15
|
]
|
|
16
16
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
from uuid import UUID
|
|
13
13
|
|
|
14
|
-
from evo.common.exceptions import CustomTypedError, EvoClientException
|
|
14
|
+
from evo.common.exceptions import BadRequestException, CustomTypedError, EvoClientException
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class SchemaIDFormatError(EvoClientException):
|
|
@@ -41,6 +41,30 @@ class SchemaValidationError(EvoClientException):
|
|
|
41
41
|
"""Failed to validate data against a geoscience object schema"""
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
class InvalidGeoscienceObjectError(CustomTypedError, BadRequestException):
|
|
45
|
+
"""The service rejected an object that does not match its geoscience object schema."""
|
|
46
|
+
|
|
47
|
+
TYPE_ID = "/geoscienceobject/schemas/invalid-object"
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
error_message = super().__str__()
|
|
51
|
+
detail = self.detail
|
|
52
|
+
if detail:
|
|
53
|
+
error_message = error_message.removesuffix(f"\nDetail: {detail}")
|
|
54
|
+
if json_path := self.content.get("json_path"):
|
|
55
|
+
error_message += f"\nInvalid value at: {json_path}"
|
|
56
|
+
if schema_path := self.content.get("schema_path"):
|
|
57
|
+
error_message += f"\nFailed schema rule: {schema_path}"
|
|
58
|
+
if detail:
|
|
59
|
+
if len(detail) > 500:
|
|
60
|
+
detail = (
|
|
61
|
+
f"{detail[:250]} ... [omitted] ... {detail[-250:]}\n"
|
|
62
|
+
"(For the full detail, catch this exception as exc and run print(exc.detail).)"
|
|
63
|
+
)
|
|
64
|
+
error_message += f"\nDetail: {detail}"
|
|
65
|
+
return error_message
|
|
66
|
+
|
|
67
|
+
|
|
44
68
|
class ObjectAlreadyExistsError(CustomTypedError):
|
|
45
69
|
TYPE_ID = "/geoscienceobject/objects/already-exists"
|
|
46
70
|
|
|
@@ -22,6 +22,7 @@ import pandas as pd
|
|
|
22
22
|
from pydantic import AliasChoices, Field
|
|
23
23
|
|
|
24
24
|
from evo.common import IFeedback
|
|
25
|
+
from evo.common.typed import FloatArrayLike3, IntArrayLike3
|
|
25
26
|
from evo.common.utils import NoFeedback
|
|
26
27
|
|
|
27
28
|
from ._model import DataLocation, SchemaLocation, SchemaModel
|
|
@@ -46,23 +47,35 @@ class Base3DGridData(BaseSpatialObjectData):
|
|
|
46
47
|
"""Base class for all 3D grid data.
|
|
47
48
|
|
|
48
49
|
Contains the common properties shared by all grid types: origin, size, rotation, and cell_data.
|
|
50
|
+
Origin and size accept their named types or three values in a list, tuple, or 1D array.
|
|
51
|
+
All inputs, including named types, are validated and normalized.
|
|
49
52
|
"""
|
|
50
53
|
|
|
51
|
-
origin: Point3
|
|
52
|
-
size: Size3i
|
|
54
|
+
origin: Point3 | FloatArrayLike3
|
|
55
|
+
size: Size3i | IntArrayLike3
|
|
53
56
|
cell_data: pd.DataFrame | None = None
|
|
54
57
|
rotation: Rotation | None = None
|
|
55
58
|
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
object.__setattr__(self, "origin", Point3.from_array_like(self.origin))
|
|
61
|
+
object.__setattr__(self, "size", Size3i.from_array_like(self.size))
|
|
62
|
+
|
|
56
63
|
|
|
57
64
|
@dataclass(kw_only=True, frozen=True)
|
|
58
65
|
class BaseRegular3DGridData(Base3DGridData):
|
|
59
66
|
"""Base class for regular 3D grid data (both masked and non-masked).
|
|
60
67
|
|
|
61
68
|
Contains the common properties shared by Regular3DGridData and RegularMasked3DGridData.
|
|
62
|
-
Adds cell_size to the base grid properties
|
|
69
|
+
Adds cell_size to the base grid properties; it accepts a Size3d or three
|
|
70
|
+
values in a list, tuple, or 1D array.
|
|
71
|
+
All inputs, including Size3d instances, are validated and normalized.
|
|
63
72
|
"""
|
|
64
73
|
|
|
65
|
-
cell_size: Size3d
|
|
74
|
+
cell_size: Size3d | FloatArrayLike3
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
super().__post_init__()
|
|
78
|
+
object.__setattr__(self, "cell_size", Size3d.from_array_like(self.cell_size))
|
|
66
79
|
|
|
67
80
|
def compute_bounding_box(self) -> BoundingBox:
|
|
68
81
|
return BoundingBox.from_regular_grid(self.origin, self.size, self.cell_size, self.rotation)
|
|
@@ -223,15 +236,20 @@ class RegularBlockModelData:
|
|
|
223
236
|
"""
|
|
224
237
|
|
|
225
238
|
name: str
|
|
226
|
-
origin: Point3
|
|
227
|
-
n_blocks: Size3i
|
|
228
|
-
block_size: Size3d
|
|
239
|
+
origin: Point3 | FloatArrayLike3
|
|
240
|
+
n_blocks: Size3i | IntArrayLike3
|
|
241
|
+
block_size: Size3d | FloatArrayLike3
|
|
229
242
|
cell_data: pd.DataFrame | None = None
|
|
230
243
|
description: str | None = None
|
|
231
244
|
coordinate_reference_system: str | None = None
|
|
232
245
|
size_unit_id: str | None = None
|
|
233
246
|
units: dict[str, str] = field(default_factory=dict)
|
|
234
247
|
|
|
248
|
+
def __post_init__(self) -> None:
|
|
249
|
+
object.__setattr__(self, "origin", Point3.from_array_like(self.origin))
|
|
250
|
+
object.__setattr__(self, "n_blocks", Size3i.from_array_like(self.n_blocks))
|
|
251
|
+
object.__setattr__(self, "block_size", Size3d.from_array_like(self.block_size))
|
|
252
|
+
|
|
235
253
|
|
|
236
254
|
@dataclass(frozen=True, kw_only=True)
|
|
237
255
|
class BlockModelData(BaseSpatialObjectData):
|
|
@@ -19,8 +19,9 @@ from dataclasses import dataclass
|
|
|
19
19
|
from typing import Annotated, Any, ClassVar
|
|
20
20
|
from uuid import UUID
|
|
21
21
|
|
|
22
|
-
from evo import jmespath
|
|
22
|
+
from evo import jmespath, logging
|
|
23
23
|
from evo.common import IContext, IFeedback, StaticContext
|
|
24
|
+
from evo.common.exceptions import error_context
|
|
24
25
|
from evo.common.utils import NoFeedback
|
|
25
26
|
from evo.objects import DownloadedObject, ObjectMetadata, ObjectReference, ObjectSchema, SchemaVersion
|
|
26
27
|
|
|
@@ -36,6 +37,8 @@ if sys.version_info >= (3, 11):
|
|
|
36
37
|
else:
|
|
37
38
|
from typing_extensions import Self
|
|
38
39
|
|
|
40
|
+
logger = logging.getLogger("object")
|
|
41
|
+
|
|
39
42
|
__all__ = [
|
|
40
43
|
"BaseObject",
|
|
41
44
|
"BaseObjectData",
|
|
@@ -254,9 +257,15 @@ class _BaseObject(SchemaModel):
|
|
|
254
257
|
|
|
255
258
|
# Take a copy to avoid the context changes affecting the object
|
|
256
259
|
context = StaticContext.create_copy(context)
|
|
257
|
-
|
|
260
|
+
description = f"{cls.__name__} '{data.name}'"
|
|
261
|
+
with error_context(f"While preparing data for {description}", logger=logger):
|
|
262
|
+
object_dict = await cls._data_to_schema(data, context)
|
|
258
263
|
object_dict["uuid"] = None # New UUID is generated by the service
|
|
259
|
-
|
|
264
|
+
target = f"path '{path}'" if path is not None else f"parent path '{parent or ''}'"
|
|
265
|
+
with error_context(
|
|
266
|
+
f"While creating {description} at {target} (schema: {object_dict['schema']})", logger=logger
|
|
267
|
+
):
|
|
268
|
+
obj = await create_geoscience_object(context, object_dict, parent, path)
|
|
260
269
|
return cls(obj)
|
|
261
270
|
|
|
262
271
|
@classmethod
|
|
@@ -285,10 +294,15 @@ class _BaseObject(SchemaModel):
|
|
|
285
294
|
workspace_id=reference.workspace_id,
|
|
286
295
|
)
|
|
287
296
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
description = f"{cls.__name__} '{data.name}'"
|
|
298
|
+
with error_context(f"While preparing replacement data for {description}", logger=logger):
|
|
299
|
+
object_dict = await cls._data_to_schema(data, reference_context)
|
|
300
|
+
with error_context(
|
|
301
|
+
f"While replacing {description} at '{reference}' (schema: {object_dict['schema']})", logger=logger
|
|
302
|
+
):
|
|
303
|
+
obj = await replace_geoscience_object(
|
|
304
|
+
reference_context, reference, object_dict, create_if_missing=create_if_missing
|
|
305
|
+
)
|
|
292
306
|
return cls(obj)
|
|
293
307
|
|
|
294
308
|
@classmethod
|
|
@@ -362,12 +376,18 @@ class _BaseObject(SchemaModel):
|
|
|
362
376
|
|
|
363
377
|
:return: A new instance with refreshed data.
|
|
364
378
|
|
|
365
|
-
Example
|
|
379
|
+
Example::
|
|
380
|
+
|
|
366
381
|
>>> # After a remote operation modifies the object...
|
|
367
382
|
>>> obj = await obj.refresh()
|
|
368
383
|
>>> obj.attributes # Now shows the latest attributes
|
|
369
384
|
"""
|
|
370
|
-
|
|
385
|
+
# metadata.url pins the current version_id, which would just re-download the same version.
|
|
386
|
+
latest_reference = ObjectReference.new(
|
|
387
|
+
environment=self.metadata.environment,
|
|
388
|
+
object_id=self.metadata.id,
|
|
389
|
+
)
|
|
390
|
+
return await self.from_reference(self._api_context, latest_reference)
|
|
371
391
|
|
|
372
392
|
def search(self, expression: str) -> Any:
|
|
373
393
|
"""Search the object metadata using a JMESPath expression.
|
|
@@ -192,7 +192,8 @@ class BlockModel(BaseSpatialObject):
|
|
|
192
192
|
:param fb: Optional feedback interface for progress reporting.
|
|
193
193
|
:return: DataFrame containing the block model data with user-friendly column names.
|
|
194
194
|
|
|
195
|
-
Example
|
|
195
|
+
Example::
|
|
196
|
+
|
|
196
197
|
>>> df = await block_model.to_dataframe()
|
|
197
198
|
>>> df.head()
|
|
198
199
|
"""
|
|
@@ -207,7 +208,8 @@ class BlockModel(BaseSpatialObject):
|
|
|
207
208
|
|
|
208
209
|
:return: A new BlockModel instance with refreshed data.
|
|
209
210
|
|
|
210
|
-
Example
|
|
211
|
+
Example::
|
|
212
|
+
|
|
211
213
|
>>> # After running kriging that adds attributes...
|
|
212
214
|
>>> block_model = await block_model.refresh()
|
|
213
215
|
>>> block_model.attributes # Now shows the new attributes
|
|
@@ -335,7 +337,8 @@ class BlockModel(BaseSpatialObject):
|
|
|
335
337
|
:param fb: Optional feedback interface for progress reporting.
|
|
336
338
|
:return: The updated BlockModel instance (refreshed from server).
|
|
337
339
|
|
|
338
|
-
Example
|
|
340
|
+
Example::
|
|
341
|
+
|
|
339
342
|
>>> from evo.blockmodels import Units
|
|
340
343
|
>>> block_model = await block_model.set_attribute_units({
|
|
341
344
|
... "Au": Units.GRAMS_PER_TONNE,
|
|
@@ -361,7 +364,8 @@ class BlockModel(BaseSpatialObject):
|
|
|
361
364
|
:param fb: Optional feedback interface for progress reporting.
|
|
362
365
|
:return: A Report instance representing the created report.
|
|
363
366
|
|
|
364
|
-
Example
|
|
367
|
+
Example::
|
|
368
|
+
|
|
365
369
|
>>> from evo.blockmodels.typed import ReportSpecificationData, ReportColumnSpec, ReportCategorySpec
|
|
366
370
|
>>> report = await block_model.create_report(ReportSpecificationData(
|
|
367
371
|
... name="Gold Resource Report",
|
|
@@ -36,6 +36,7 @@ class Regular3DGridData(BaseRegular3DGridData):
|
|
|
36
36
|
vertex_data: pd.DataFrame | None = None
|
|
37
37
|
|
|
38
38
|
def __post_init__(self):
|
|
39
|
+
super().__post_init__()
|
|
39
40
|
if self.cell_data is not None and self.cell_data.shape[0] != self.size.total_size:
|
|
40
41
|
raise ObjectValidationError(
|
|
41
42
|
f"The number of rows in the cell_data dataframe ({self.cell_data.shape[0]}) does not match the number of cells in the grid ({self.size.nx * self.size.ny * self.size.nz})."
|
|
@@ -43,6 +43,7 @@ class RegularMasked3DGridData(BaseRegular3DGridData):
|
|
|
43
43
|
mask: np.ndarray
|
|
44
44
|
|
|
45
45
|
def __post_init__(self):
|
|
46
|
+
super().__post_init__()
|
|
46
47
|
if self.mask.shape[0] != self.size.total_size:
|
|
47
48
|
raise ObjectValidationError(
|
|
48
49
|
f"The number of rows in the mask ({self.mask.shape[0]}) does not match the number of cells in the grid ({self.size.nx * self.size.ny * self.size.nz})."
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
+
from numbers import Real
|
|
15
16
|
from typing import Annotated, Any, overload
|
|
16
17
|
|
|
17
18
|
import numpy as np
|
|
@@ -20,7 +21,7 @@ import pydantic
|
|
|
20
21
|
|
|
21
22
|
# Import basic geometry types from evo.common and re-export
|
|
22
23
|
from evo.common.crs import EpsgCode, parse_crs
|
|
23
|
-
from evo.common.typed import Point3, Size3d, Size3i
|
|
24
|
+
from evo.common.typed import FloatArrayLike3, Point3, Size3d, Size3i, _validate_array_like
|
|
24
25
|
|
|
25
26
|
__all__ = [
|
|
26
27
|
"BoundingBox",
|
|
@@ -204,12 +205,26 @@ class BoundingBox:
|
|
|
204
205
|
|
|
205
206
|
@dataclass(frozen=True)
|
|
206
207
|
class Rotation:
|
|
207
|
-
"""A
|
|
208
|
+
"""A clockwise intrinsic ZXZ Euler rotation in degrees.
|
|
209
|
+
|
|
210
|
+
Dip azimuth [0, 360], dip [0, 180], and pitch [0, 360] rotate about
|
|
211
|
+
successive local Z, X, and Z axes, respectively. For column vectors,
|
|
212
|
+
the rotation matrix is Rz(dip azimuth) @ Rx(dip) @ Rz(pitch).
|
|
213
|
+
"""
|
|
208
214
|
|
|
209
215
|
dip_azimuth: float
|
|
210
216
|
dip: float
|
|
211
217
|
pitch: float
|
|
212
218
|
|
|
219
|
+
def __post_init__(self) -> None:
|
|
220
|
+
for name, value, lower, upper in (
|
|
221
|
+
("dip_azimuth", self.dip_azimuth, 0, 360),
|
|
222
|
+
("dip", self.dip, 0, 180),
|
|
223
|
+
("pitch", self.pitch, 0, 360),
|
|
224
|
+
):
|
|
225
|
+
if not isinstance(value, Real) or isinstance(value, bool) or not lower <= value <= upper:
|
|
226
|
+
raise ValueError(f"{name} must be a finite angle between {lower} and {upper} degrees (inclusive)")
|
|
227
|
+
|
|
213
228
|
def as_rotation_matrix(self) -> np.ndarray:
|
|
214
229
|
"""Convert the rotation to a rotation matrix.
|
|
215
230
|
|
|
@@ -246,6 +261,12 @@ class Rotation:
|
|
|
246
261
|
# Combined intrinsic rotations: dip_azimuth -> dip -> pitch
|
|
247
262
|
return dip_azimuth_rotation_matrix @ dip_rotation_matrix @ pitch_rotation_matrix
|
|
248
263
|
|
|
264
|
+
@classmethod
|
|
265
|
+
def from_array_like(cls, value: Rotation | FloatArrayLike3) -> Rotation:
|
|
266
|
+
if isinstance(value, Rotation):
|
|
267
|
+
value = (value.dip_azimuth, value.dip, value.pitch)
|
|
268
|
+
return cls(*_validate_array_like(value, kind=Real, positive=False))
|
|
269
|
+
|
|
249
270
|
|
|
250
271
|
@dataclass
|
|
251
272
|
class EllipsoidRanges:
|
|
@@ -256,9 +277,9 @@ class EllipsoidRanges:
|
|
|
256
277
|
minor: float
|
|
257
278
|
|
|
258
279
|
def __init__(self, major: float, semi_major: float, minor: float):
|
|
259
|
-
self.major =
|
|
260
|
-
|
|
261
|
-
|
|
280
|
+
self.major, self.semi_major, self.minor = _validate_array_like(
|
|
281
|
+
(major, semi_major, minor), kind=Real, positive=True
|
|
282
|
+
)
|
|
262
283
|
|
|
263
284
|
def to_dict(self) -> dict[str, Any]:
|
|
264
285
|
return {"major": self.major, "semi_major": self.semi_major, "minor": self.minor}
|
|
@@ -270,17 +291,37 @@ class EllipsoidRanges:
|
|
|
270
291
|
minor=self.minor * factor,
|
|
271
292
|
)
|
|
272
293
|
|
|
294
|
+
@classmethod
|
|
295
|
+
def from_array_like(cls, value: EllipsoidRanges | FloatArrayLike3) -> EllipsoidRanges:
|
|
296
|
+
if isinstance(value, EllipsoidRanges):
|
|
297
|
+
value = (value.major, value.semi_major, value.minor)
|
|
298
|
+
return cls(*_validate_array_like(value, kind=Real, positive=True))
|
|
299
|
+
|
|
273
300
|
|
|
274
301
|
@dataclass
|
|
275
302
|
class Ellipsoid:
|
|
276
|
-
"""An ellipsoid defining a spatial region.
|
|
303
|
+
"""An ellipsoid defining a spatial region.
|
|
304
|
+
|
|
305
|
+
Ranges are ordered major, semi-major, minor. Rotation angles are in
|
|
306
|
+
degrees and ordered dip azimuth [0, 360], dip [0, 180], pitch [0, 360].
|
|
307
|
+
They specify clockwise intrinsic rotations about the successive Z, X,
|
|
308
|
+
and Z axes, respectively.
|
|
309
|
+
|
|
310
|
+
Ranges accept an EllipsoidRanges instance or a three-value list, tuple,
|
|
311
|
+
or 1D NumPy array. Rotation accepts a Rotation instance or the same
|
|
312
|
+
three-value sequence forms; omitting it applies no rotation.
|
|
313
|
+
"""
|
|
277
314
|
|
|
278
315
|
ranges: EllipsoidRanges
|
|
279
316
|
_rotation: Rotation | None = None
|
|
280
317
|
|
|
281
|
-
def __init__(
|
|
282
|
-
self
|
|
283
|
-
|
|
318
|
+
def __init__(
|
|
319
|
+
self,
|
|
320
|
+
ranges: EllipsoidRanges | FloatArrayLike3,
|
|
321
|
+
rotation: Rotation | FloatArrayLike3 | None = None,
|
|
322
|
+
):
|
|
323
|
+
self.ranges = EllipsoidRanges.from_array_like(ranges)
|
|
324
|
+
self._rotation = Rotation.from_array_like(rotation) if rotation is not None else None
|
|
284
325
|
|
|
285
326
|
@property
|
|
286
327
|
def rotation(self) -> Rotation:
|
|
@@ -70,7 +70,8 @@ class VariogramStructure:
|
|
|
70
70
|
- Creating search ellipsoids via scaled()
|
|
71
71
|
- Kriging search neighborhoods
|
|
72
72
|
|
|
73
|
-
Example
|
|
73
|
+
Example::
|
|
74
|
+
|
|
74
75
|
>>> # Get ellipsoid from variogram structure
|
|
75
76
|
>>> var_ell = variogram.structures[0].to_ellipsoid()
|
|
76
77
|
>>>
|
|
@@ -277,7 +278,8 @@ class VariogramData(BaseObjectData):
|
|
|
277
278
|
- `modelling_space`: Set to "data" for original units or "normalscore" for gaussian space
|
|
278
279
|
- `data_variance`: Should match the sill value for non-normalized data
|
|
279
280
|
|
|
280
|
-
Example using typed structures (recommended)
|
|
281
|
+
Example using typed structures (recommended)::
|
|
282
|
+
|
|
281
283
|
>>> data = VariogramData(
|
|
282
284
|
... name="My Variogram",
|
|
283
285
|
... sill=1.0,
|
|
@@ -407,7 +409,8 @@ class Variogram(BaseObject):
|
|
|
407
409
|
Returns:
|
|
408
410
|
Ellipsoid configured with the structure's anisotropy ranges and rotation.
|
|
409
411
|
|
|
410
|
-
Example
|
|
412
|
+
Example::
|
|
413
|
+
|
|
411
414
|
>>> # Get ellipsoid from structure with largest range (default)
|
|
412
415
|
>>> var_ell = variogram.get_ellipsoid()
|
|
413
416
|
>>>
|
|
@@ -475,7 +478,8 @@ class Variogram(BaseObject):
|
|
|
475
478
|
Returns:
|
|
476
479
|
Tuple of (major_curve, semi_major_curve, minor_curve) as VariogramCurveData.
|
|
477
480
|
|
|
478
|
-
Example with Plotly
|
|
481
|
+
Example with Plotly::
|
|
482
|
+
|
|
479
483
|
>>> major, semi_maj, minor = variogram.get_principal_directions()
|
|
480
484
|
>>> import plotly.graph_objects as go
|
|
481
485
|
>>> fig = go.Figure()
|
|
@@ -549,7 +553,8 @@ class Variogram(BaseObject):
|
|
|
549
553
|
Returns:
|
|
550
554
|
Tuple of (distance, semivariance) as numpy arrays, suitable for plotting.
|
|
551
555
|
|
|
552
|
-
Example with Plotly
|
|
556
|
+
Example with Plotly::
|
|
557
|
+
|
|
553
558
|
>>> distance, semivariance = variogram.get_direction(azimuth=45, dip=30)
|
|
554
559
|
>>> import plotly.graph_objects as go
|
|
555
560
|
>>> fig = go.Figure()
|
|
@@ -557,7 +562,8 @@ class Variogram(BaseObject):
|
|
|
557
562
|
>>> fig.update_layout(xaxis_title='Distance', yaxis_title='Semivariance')
|
|
558
563
|
>>> fig.show()
|
|
559
564
|
|
|
560
|
-
Example with Matplotlib
|
|
565
|
+
Example with Matplotlib::
|
|
566
|
+
|
|
561
567
|
>>> distance, semivariance = variogram.get_direction(azimuth=0, dip=0)
|
|
562
568
|
>>> import matplotlib.pyplot as plt
|
|
563
569
|
>>> plt.plot(distance, semivariance)
|
|
@@ -19,7 +19,7 @@ import numpy as np
|
|
|
19
19
|
|
|
20
20
|
from evo import logging
|
|
21
21
|
from evo.common import APIConnector, Environment, ICache, IFeedback, StaticContext
|
|
22
|
-
from evo.common.exceptions import StorageFileNotFoundError
|
|
22
|
+
from evo.common.exceptions import StorageFileNotFoundError, error_context
|
|
23
23
|
from evo.common.io.exceptions import DataExistsError
|
|
24
24
|
from evo.common.utils import NoFeedback, PartialFeedback, split_feedback
|
|
25
25
|
|
|
@@ -143,22 +143,24 @@ class ObjectDataClient:
|
|
|
143
143
|
n_names = len(names)
|
|
144
144
|
logger.debug(f"Found {n_names} data reference{'' if n_names == 1 else 's'}")
|
|
145
145
|
n_uploaded = 0
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
146
|
+
with error_context("While uploading referenced data", logger=logger):
|
|
147
|
+
async for ctx in ObjectDataUpload._create_multiple(
|
|
148
|
+
connector=self._connector,
|
|
149
|
+
environment=self._environment,
|
|
150
|
+
names=names,
|
|
151
|
+
):
|
|
152
|
+
if fb is not NoFeedback:
|
|
153
|
+
fb_part = PartialFeedback(parent=fb, start=n_uploaded / n_names, end=(n_uploaded + 1) / n_names)
|
|
154
|
+
else:
|
|
155
|
+
fb_part = NoFeedback
|
|
156
|
+
|
|
157
|
+
with error_context(f"While uploading referenced data '{ctx.name}'", logger=logger):
|
|
158
|
+
data_file = self.cache_location / ctx.name
|
|
159
|
+
logger.debug(f"Uploading data: {ctx.name}")
|
|
160
|
+
if not data_file.exists():
|
|
161
|
+
raise StorageFileNotFoundError(f"Object data file not found in cache: {data_file}")
|
|
162
|
+
await ctx.upload_from_path(filename=data_file, transport=self._connector.transport, fb=fb_part)
|
|
163
|
+
n_uploaded += 1
|
|
162
164
|
|
|
163
165
|
logger.debug(
|
|
164
166
|
f"Uploaded {n_uploaded} data reference{'' if n_uploaded == 1 else 's'}, skipped {n_names - n_uploaded}"
|
|
@@ -183,9 +185,10 @@ class ObjectDataClient:
|
|
|
183
185
|
"""
|
|
184
186
|
from .table_formats import get_known_format
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
with error_context("While preparing an Arrow table for Evo Parquet storage", logger=logger):
|
|
189
|
+
if not isinstance(table_format, KnownTableFormat):
|
|
190
|
+
table_format = get_known_format(table, table_formats=table_format)
|
|
191
|
+
table_info = table_format.save_table(table=table, destination=self.cache_location)
|
|
189
192
|
return table_info
|
|
190
193
|
|
|
191
194
|
async def upload_table(
|
|
@@ -210,11 +213,12 @@ class ObjectDataClient:
|
|
|
210
213
|
"""
|
|
211
214
|
table_info = self.save_table(table, table_format)
|
|
212
215
|
upload = ObjectDataUpload(connector=self._connector, environment=self._environment, name=table_info["data"])
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
with error_context(f"While uploading table data '{table_info['data']}'", logger=logger):
|
|
217
|
+
try:
|
|
218
|
+
await upload.upload_from_cache(cache=self._cache, transport=self._connector.transport, fb=fb)
|
|
219
|
+
except DataExistsError:
|
|
220
|
+
logger.debug(f"Data not uploaded because data already exists (label: {table_info['data']})")
|
|
221
|
+
fb.progress(1)
|
|
218
222
|
return table_info
|
|
219
223
|
|
|
220
224
|
async def upload_category_table(self, table: pa.Table, fb: IFeedback = NoFeedback) -> CategoryInfo:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|