Skip to content

Commit

Permalink
Merge remote-tracking branch 'equinor/main' into 2d-viewer-module
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Dec 6, 2024
2 parents 3104717 + 0eef0de commit bbca246
Show file tree
Hide file tree
Showing 177 changed files with 8,084 additions and 3,660 deletions.
4 changes: 2 additions & 2 deletions backend_py/primary/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from primary.routers.general import router as general_router
from primary.routers.graph.router import router as graph_router
from primary.routers.grid3d.router import router as grid3d_router
from primary.routers.group_tree.router import router as group_tree_router
from primary.routers.flow_network.router import router as flow_network_router
from primary.routers.inplace_volumetrics.router import router as inplace_volumetrics_router
from primary.routers.observations.router import router as observations_router
from primary.routers.parameters.router import router as parameters_router
Expand Down Expand Up @@ -77,7 +77,7 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app.include_router(surface_router, prefix="/surface", tags=["surface"])
app.include_router(parameters_router, prefix="/parameters", tags=["parameters"])
app.include_router(grid3d_router, prefix="/grid3d", tags=["grid3d"])
app.include_router(group_tree_router, prefix="/group_tree", tags=["group_tree"])
app.include_router(flow_network_router, prefix="/flow_network", tags=["flow_network"])
app.include_router(pvt_router, prefix="/pvt", tags=["pvt"])
app.include_router(well_completions_router, prefix="/well_completions", tags=["well_completions"])
app.include_router(well_router, prefix="/well", tags=["well"])
Expand Down
3 changes: 3 additions & 0 deletions backend_py/primary/primary/routers/explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class EnsembleInfo(BaseModel):
class EnsembleDetails(BaseModel):
name: str
field_identifier: str
stratigraphic_column_identifier: str
case_name: str
case_uuid: str
realizations: Sequence[int]
Expand Down Expand Up @@ -91,6 +92,7 @@ async def get_ensemble_details(
case_name = await case_inspector.get_case_name_async()
realizations = await case_inspector.get_realizations_in_iteration_async(ensemble_name)
field_identifiers = await case_inspector.get_field_identifiers_async()
stratigraphic_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async()

if len(field_identifiers) != 1:
raise NotImplementedError("Multiple field identifiers not supported")
Expand All @@ -101,4 +103,5 @@ async def get_ensemble_details(
case_uuid=case_uuid,
realizations=realizations,
field_identifier=field_identifiers[0],
stratigraphic_column_identifier=stratigraphic_column_identifier,
)
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import logging
from fastapi import APIRouter, Depends, Query
from webviz_pkg.core_utils.perf_timer import PerfTimer
from primary.auth.auth_helper import AuthHelper
from primary.services.group_tree_assembler.group_tree_assembler import GroupTreeAssembler

from primary.services.flow_network_assembler.flow_network_assembler import FlowNetworkAssembler
from primary.services.flow_network_assembler.flow_network_types import NetworkModeOptions, NodeType
from primary.services.sumo_access.group_tree_access import GroupTreeAccess
from primary.services.sumo_access.group_tree_types import TreeModeOptions, NodeType
from primary.services.sumo_access.summary_access import Frequency, SummaryAccess
from primary.services.utils.authenticated_user import AuthenticatedUser

from . import schemas

from webviz_pkg.core_utils.perf_timer import PerfTimer
import logging

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/realization_group_tree_data/")
async def get_realization_group_tree_data(
@router.get("/realization_flow_network/")
async def get_realization_flow_network(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
Expand All @@ -26,7 +26,7 @@ async def get_realization_group_tree_data(
resampling_frequency: schemas.Frequency = Query(description="Resampling frequency"),
node_type_set: set[schemas.NodeType] = Query(description="Node types"),
# fmt:on
) -> schemas.GroupTreeData:
) -> schemas.FlowNetworkData:
timer = PerfTimer()

group_tree_access = await GroupTreeAccess.from_case_uuid_async(
Expand All @@ -38,33 +38,33 @@ async def get_realization_group_tree_data(
summary_frequency = Frequency.YEARLY

# Convert to NodeType enum in group_tree_types
unique_node_types = set([NodeType(elm.value) for elm in node_type_set])
unique_node_types = {NodeType(elm.value) for elm in node_type_set}

group_tree_data = GroupTreeAssembler(
network_assembler = FlowNetworkAssembler(
group_tree_access=group_tree_access,
summary_access=summary_access,
realization=realization,
summary_frequency=summary_frequency,
node_types=unique_node_types,
group_tree_mode=TreeModeOptions.SINGLE_REAL,
flow_network_mode=NetworkModeOptions.SINGLE_REAL,
)

timer.lap_ms()
await group_tree_data.fetch_and_initialize_async()
await network_assembler.fetch_and_initialize_async()
initialize_time_ms = timer.lap_ms()

(
dated_trees,
dated_networks,
edge_metadata,
node_metadata,
) = await group_tree_data.create_dated_trees_and_metadata_lists()
) = await network_assembler.create_dated_networks_and_metadata_lists()
create_data_time_ms = timer.lap_ms()

LOGGER.info(
f"Group tree data for single realization fetched and processed in: {timer.elapsed_ms()}ms "
f"(initialize={initialize_time_ms}ms, create group tree={create_data_time_ms}ms)"
)

return schemas.GroupTreeData(
edge_metadata_list=edge_metadata, node_metadata_list=node_metadata, dated_trees=dated_trees
return schemas.FlowNetworkData(
edgeMetadataList=edge_metadata, nodeMetadataList=node_metadata, datedNetworks=dated_networks
)
36 changes: 36 additions & 0 deletions backend_py/primary/primary/routers/flow_network/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from enum import Enum, StrEnum

from pydantic import BaseModel, ConfigDict
from primary.services.flow_network_assembler.flow_network_types import DatedFlowNetwork, FlowNetworkMetadata


class Frequency(str, Enum):
DAILY = "DAILY"
WEEKLY = "WEEKLY"
MONTHLY = "MONTHLY"
QUARTERLY = "QUARTERLY"
YEARLY = "YEARLY"


class StatOption(str, Enum):
MEAN = "MEAN"
P10 = "P10"
P90 = "P90"
P50 = "P50"
MIN = "MIN"
MAX = "MAX"


# ! Copy of the flow network service NodeType enum
class NodeType(StrEnum):
PROD = "prod"
INJ = "inj"
OTHER = "other"


class FlowNetworkData(BaseModel):
model_config = ConfigDict(revalidate_instances="always")

edgeMetadataList: list[FlowNetworkMetadata]
nodeMetadataList: list[FlowNetworkMetadata]
datedNetworks: list[DatedFlowNetwork]
34 changes: 0 additions & 34 deletions backend_py/primary/primary/routers/group_tree/schemas.py

This file was deleted.

13 changes: 7 additions & 6 deletions backend_py/primary/primary/routers/polygons/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from webviz_pkg.core_utils.perf_timer import PerfTimer

from primary.auth.auth_helper import AuthHelper
from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access
from primary.services.smda_access.stratigraphy_access import StratigraphyAccess
from primary.services.smda_access.drogon import DrogonSmdaAccess
from primary.services.smda_access import SmdaAccess
from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy
from primary.services.sumo_access.case_inspector import CaseInspector
from primary.services.sumo_access.polygons_access import PolygonsAccess
Expand Down Expand Up @@ -34,14 +34,15 @@ async def get_polygons_directory(
polygons_dir = await access.get_polygons_directory_async()

case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid)
field_identifiers = await case_inspector.get_field_identifiers_async()
strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async()
strat_access: Union[StratigraphyAccess, _mocked_stratigraphy_access.StratigraphyAccess]
smda_access: Union[SmdaAccess, DrogonSmdaAccess]

if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN":
strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token())
smda_access = DrogonSmdaAccess()
else:
strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token())
strat_units = await strat_access.get_stratigraphic_units(strat_column_identifier)
smda_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifiers[0])
strat_units = await smda_access.get_stratigraphic_units(strat_column_identifier)
sorted_stratigraphic_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units)

return converters.to_api_polygons_directory(polygons_dir, sorted_stratigraphic_surfaces)
Expand Down
1 change: 1 addition & 0 deletions backend_py/primary/primary/routers/polygons/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PolygonsAttributeType(str, Enum):
PINCHOUT = "pinchout" # Values are pinchouts
SUBCROP = "subcrop" # Values are subcrops
FAULT_LINES = "fault_lines" # Values are fault lines
NAMED_AREA = "named_area" # Values are named areas, e.g. CCS containment polygons


class PolygonsMeta(BaseModel):
Expand Down
50 changes: 50 additions & 0 deletions backend_py/primary/primary/routers/seismic/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import List

import orjson
import numpy as np
import xtgeo

from . import schemas


def surface_to_float32_array(values: np.ndarray) -> List[float]:
values = values.astype(np.float32)
np.ma.set_fill_value(values, np.nan)
values = np.ma.filled(values)

# Rotate 90 deg left.
# This will cause the width of to run along the X axis
# and height of along Y axis (starting from bottom.)
values = np.rot90(values)

return values.flatten().tolist()


def to_api_surface_data(
xtgeo_surf: xtgeo.RegularSurface, property_values: np.ndarray
) -> schemas.SurfaceMeshAndProperty:
"""
Create API SurfaceData from xtgeo regular surface
"""
float32_mesh = surface_to_float32_array(xtgeo_surf.values)
float32_property = surface_to_float32_array(property_values)

return schemas.SurfaceMeshAndProperty(
x_ori=xtgeo_surf.xori,
y_ori=xtgeo_surf.yori,
x_count=xtgeo_surf.ncol,
y_count=xtgeo_surf.nrow,
x_inc=xtgeo_surf.xinc,
y_inc=xtgeo_surf.yinc,
x_min=xtgeo_surf.xmin,
x_max=xtgeo_surf.xmax,
y_min=xtgeo_surf.ymin,
y_max=xtgeo_surf.ymax,
mesh_value_min=xtgeo_surf.values.min(),
mesh_value_max=xtgeo_surf.values.max(),
property_value_min=property_values.min(),
property_value_max=property_values.max(),
rot_deg=xtgeo_surf.rotation,
mesh_data=orjson.dumps(float32_mesh).decode("utf-8"), # pylint: disable=maybe-no-member
property_data=orjson.dumps(float32_property).decode("utf-8"), # pylint: disable=maybe-no-member
)
20 changes: 20 additions & 0 deletions backend_py/primary/primary/routers/seismic/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,23 @@ class SeismicFenceData(BaseModel):
num_samples_per_trace: int
min_fence_depth: float
max_fence_depth: float


class SurfaceMeshAndProperty(BaseModel):
x_ori: float
y_ori: float
x_count: int
y_count: int
x_inc: float
y_inc: float
x_min: float
x_max: float
y_min: float
y_max: float
mesh_value_min: float
mesh_value_max: float
property_value_min: float
property_value_max: float
rot_deg: float
mesh_data: str
property_data: str
20 changes: 20 additions & 0 deletions backend_py/primary/primary/routers/surface/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from primary.services.utils.surface_intersect_with_polyline import XtgeoSurfaceIntersectionResult
from primary.services.utils.surface_to_float32 import surface_to_float32_numpy_array
from primary.services.utils.surface_to_png import surface_to_png_bytes_optimized
from primary.services.smda_access import StratigraphicUnit

from . import schemas

Expand Down Expand Up @@ -174,3 +175,22 @@ def to_api_surface_intersection(
z_points=xtgeo_surface_intersection.zval,
cum_lengths=xtgeo_surface_intersection.distance,
)


def to_api_stratigraphic_unit(
stratigraphic_unit: StratigraphicUnit,
) -> schemas.StratigraphicUnit:
return schemas.StratigraphicUnit(
identifier=stratigraphic_unit.identifier,
top=stratigraphic_unit.top,
base=stratigraphic_unit.base,
stratUnitLevel=stratigraphic_unit.strat_unit_level,
stratUnitType=stratigraphic_unit.strat_unit_type,
topAge=stratigraphic_unit.top_age,
baseAge=stratigraphic_unit.base_age,
stratUnitParent=stratigraphic_unit.strat_unit_parent,
colorR=stratigraphic_unit.color_r,
colorG=stratigraphic_unit.color_g,
colorB=stratigraphic_unit.color_b,
lithologyType=stratigraphic_unit.lithology_type,
)
31 changes: 25 additions & 6 deletions backend_py/primary/primary/routers/surface/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

from primary.services.sumo_access.case_inspector import CaseInspector
from primary.services.sumo_access.surface_access import SurfaceAccess
from primary.services.smda_access.stratigraphy_access import StratigraphyAccess, StratigraphicUnit
from primary.services.smda_access import SmdaAccess, StratigraphicUnit
from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy
from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access
from primary.services.smda_access.drogon import DrogonSmdaAccess
from primary.services.utils.statistic_function import StatisticFunction
from primary.services.utils.surface_intersect_with_polyline import intersect_surface_with_polyline
from primary.services.utils.authenticated_user import AuthenticatedUser
Expand Down Expand Up @@ -295,22 +295,41 @@ async def get_misfit_surface_data(
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED)


@router.get("/stratigraphic_units")
async def get_stratigraphic_units(
# fmt:off
response: Response,
authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)],
case_uuid: Annotated[str, Query(description="Sumo case uuid")],
# fmt:on
) -> list[schemas.StratigraphicUnit]:
perf_metrics = ResponsePerfMetrics(response)

strat_units = await _get_stratigraphic_units_for_case_async(authenticated_user, case_uuid)
api_strat_units = [converters.to_api_stratigraphic_unit(strat_unit) for strat_unit in strat_units]

LOGGER.info(f"Got stratigraphic units in: {perf_metrics.to_string()}")

return api_strat_units


async def _get_stratigraphic_units_for_case_async(
authenticated_user: AuthenticatedUser, case_uuid: str
) -> list[StratigraphicUnit]:
perf_metrics = PerfMetrics()

case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid)
field_identifiers = await case_inspector.get_field_identifiers_async()
strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async()
perf_metrics.record_lap("get-strat-ident")

strat_access: StratigraphyAccess | _mocked_stratigraphy_access.StratigraphyAccess
smda_access: SmdaAccess | DrogonSmdaAccess
if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN":
strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token())
smda_access = DrogonSmdaAccess()
else:
strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token())
smda_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifiers[0])

strat_units = await strat_access.get_stratigraphic_units(strat_column_identifier)
strat_units = await smda_access.get_stratigraphic_units(strat_column_identifier)
perf_metrics.record_lap("get-strat-units")

LOGGER.info(f"Got stratigraphic units for case in : {perf_metrics.to_string()}")
Expand Down
Loading

0 comments on commit bbca246

Please sign in to comment.