Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 10 additions & 25 deletions surface_apps/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,28 @@
import sys
import tempfile
from abc import abstractmethod
from json import load
from pathlib import Path

from geoapps_utils.driver.data import BaseData
from geoapps_utils.driver.driver import BaseDriver
from geoapps_utils.base import Driver, Options
from geoh5py.groups import UIJsonGroup
from geoh5py.objects import ObjectBase
from geoh5py.shared.utils import fetch_active_workspace
from geoh5py.shared.utils import fetch_active_workspace, stringify
from geoh5py.ui_json import InputFile


logger = logging.getLogger(__name__)


class BaseSurfaceDriver(BaseDriver):
class BaseSurfaceDriver(Driver):
"""
Driver for the surface application.

:param parameters: Application parameters.
"""

_parameter_class: type[BaseData]
_parameter_class: type[Options]

def __init__(self, parameters: BaseData | InputFile):
def __init__(self, parameters: Options | InputFile):
self._out_group: UIJsonGroup | None = None

if isinstance(parameters, InputFile):
Expand All @@ -59,9 +57,7 @@ def out_group(self) -> UIJsonGroup | None:
workspace=workspace,
name=self.params.title,
)
self._out_group.options = InputFile.stringify( # type: ignore
InputFile.demote(self.params.input_file.ui_json)
)
self._out_group.options = stringify(self.params.input_file.ui_json)

return self._out_group

Expand Down Expand Up @@ -91,27 +87,16 @@ def run(self):
self.store()

@property
def params(self) -> BaseData:
def params(self) -> Options:
"""Application parameters."""
return self._params

@params.setter
def params(self, val: BaseData):
if not isinstance(val, BaseData):
raise TypeError("Parameters must be a BaseData subclass.")
def params(self, val: Options):
if not isinstance(val, Options):
raise TypeError("Parameters must be an Options subclass.")
self._params = val

@classmethod
def start(cls, filepath: str | Path, driver_class=None, **kwargs):
with open(filepath, encoding="utf-8") as jsonfile:
uijson = load(jsonfile)

if driver_class is None:
module = __import__(uijson["run_command"], fromlist=["Driver"])
driver_class = module.Driver

super().start(filepath, driver_class=driver_class, **kwargs)

def add_ui_json(self, entity: ObjectBase | UIJsonGroup) -> None:
"""
Add ui.json file to entity.
Expand Down
26 changes: 20 additions & 6 deletions surface_apps/iso_surfaces/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
from typing import ClassVar

import numpy as np
from geoapps_utils.driver.data import BaseData
from geoapps_utils.base import Options
from geoh5py.data import Data
from geoh5py.groups import UIJsonGroup
from geoh5py.objects import Points, Surface
from geoh5py.objects import BlockModel, Points, Surface
from geoh5py.objects.cell_object import CellObject
from geoh5py.objects.grid_object import GridObject
from geoh5py.ui_json.utils import str2list
from pydantic import ConfigDict, field_validator
from pydantic import BaseModel, ConfigDict, field_validator

from surface_apps import assets_path


class IsoSurfaceSourceParameters(BaseData):
class IsoSurfaceSourceParameters(BaseModel):
"""
Source parameters providing input data to the driver.

Expand All @@ -41,8 +41,22 @@ class IsoSurfaceSourceParameters(BaseData):
data: Data
horizon: Surface | None = None

@field_validator("objects", mode="before")
@classmethod
def no_single_layer_grids(cls, value):
"""Ensure a grid has more than a single layer in any dimension."""

if isinstance(value, BlockModel):
n_cells = [len(getattr(value, f"{k}_cell_delimiters")) - 1 for k in "uvz"]
if any(n == 1 for n in n_cells):
raise ValueError(
"Grid source cannot be a single layer in any dimension."
)

return value


class IsoSurfaceDetectionParameters(BaseData):
class IsoSurfaceDetectionParameters(BaseModel):
"""
Contour specification parameters.

Expand Down Expand Up @@ -123,7 +137,7 @@ def contours(self) -> list[float]:
return contours


class IsoSurfaceParameters(BaseData):
class IsoSurfaceParameters(Options):
"""
Contour parameters for use with `contours.driver`.

Expand Down
38 changes: 38 additions & 0 deletions tests/run_tests/iso_surfaces_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
from pathlib import Path

import numpy as np
import pytest
from geoapps_utils.utils.importing import GeoAppsError
from geoh5py.objects import BlockModel, Points, Surface
from geoh5py.workspace import Workspace

from surface_apps.iso_surfaces.driver import Driver as IsoSurfacesDriver
from surface_apps.iso_surfaces.params import IsoSurfaceParameters


# pylint: disable=too-many-locals
Expand Down Expand Up @@ -215,3 +218,38 @@ def test_clipping_horizon(tmp_path: Path):
)

assert np.all(surface.vertices[:, -1] <= 30)


def test_single_layer_grid(tmp_path):
with Workspace(tmp_path / "iso_test.geoh5") as ws:
grid = BlockModel.create(
ws,
name="single_layer_grid",
u_cell_delimiters=np.linspace(0, 10, 11),
v_cell_delimiters=np.linspace(0, 10, 11),
z_cell_delimiters=np.array([0.0, 1.0]),
origin=[0, 0, 0],
)
data = grid.add_data(
{
"elevation": {
"values": np.random.rand(grid.n_cells) * 100,
"data_type": "Float",
"association": "Cell",
}
}
)

with pytest.raises(GeoAppsError, match="cannot be a single layer"):
IsoSurfaceParameters.build(
{
"geoh5": ws,
"objects": grid,
"data": data,
"interval_min": 0.0,
"interval_max": 100.0,
"interval_spacing": 20.0,
"max_distance": 50.0,
"resolution": 5.0,
}
)
Loading