Skip to content

Commit

Permalink
Merge remote-tracking branch 'equinor/main' into add-struct-unc-layer
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Jun 24, 2024
2 parents 541339f + fdee465 commit 3be75fe
Show file tree
Hide file tree
Showing 29 changed files with 1,202 additions and 516 deletions.
11 changes: 7 additions & 4 deletions backend_py/libs/core_utils/webviz_pkg/core_utils/perf_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ def elapsed_ms(self) -> int:
"""Get the time elapsed since the start, in milliseconds"""
return int(1000 * self.elapsed_s())

def lap_s(self) -> float:
def lap_s(self, reset_lap_timer: bool = True) -> float:
"""Get elapsed time since last lap, in seconds"""
time_now = time.perf_counter()
elapsed = time_now - self._lap_s
self._lap_s = time_now

if (reset_lap_timer):
self._lap_s = time_now

return elapsed

def lap_ms(self) -> int:
def lap_ms(self, reset_lap_timer: bool = True) -> int:
"""Get elapsed time since last lap, in milliseconds"""
return int(1000 * self.lap_s())
return int(1000 * self.lap_s(reset_lap_timer=reset_lap_timer))
97 changes: 39 additions & 58 deletions backend_py/primary/primary/routers/surface/converters.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from typing import List

import numpy as np
import xtgeo
from numpy.typing import NDArray
from webviz_pkg.core_utils.b64 import b64_encode_float_array_as_float32

from primary.services.smda_access.types import StratigraphicSurface
from primary.services.sumo_access.surface_types import SurfaceMeta as SumoSurfaceMeta
from primary.services.sumo_access.surface_types import SurfaceMetaSet
from primary.services.utils.surface_intersect_with_polyline import XtgeoSurfaceIntersectionPolyline
from primary.services.utils.surface_intersect_with_polyline import XtgeoSurfaceIntersectionResult
from primary.services.utils.surface_to_float32 import surface_to_float32_numpy_array
Expand Down Expand Up @@ -53,66 +51,49 @@ def to_api_surface_data(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData
)


def to_api_surface_directory(
sumo_surface_dir: List[SumoSurfaceMeta], stratigraphical_names: List[StratigraphicSurface]
) -> List[schemas.SurfaceMeta]:
def to_api_surface_meta_set(
sumo_surf_meta_set: SurfaceMetaSet, ordered_stratigraphic_surfaces: list[StratigraphicSurface]
) -> schemas.SurfaceMetaSet:
"""
Convert Sumo surface directory to API surface directory
Convert surface metadata directory from Sumo access layer to API surface directory
At the same time, we build a list of the surface names ordered by stratigraphy where the names of
non-stratigraphic surfaces is appended at the end of the list (in alphabetical order).
"""

surface_metas = _sort_by_stratigraphical_order(sumo_surface_dir, stratigraphical_names)
return surface_metas


def _sort_by_stratigraphical_order(
sumo_surface_metas: List[SumoSurfaceMeta], stratigraphic_surfaces: List[StratigraphicSurface]
) -> List[schemas.SurfaceMeta]:
"""Sort the Sumo surface meta list by the order they appear in the stratigraphic column.
Non-stratigraphical surfaces are appended at the end of the list."""

surface_metas_with_official_strat_name = []
surface_metas_with_custom_names = []

for strat_surface in stratigraphic_surfaces:
for sumo_surface_meta in sumo_surface_metas:
if sumo_surface_meta.name == strat_surface.name:
surface_meta = schemas.SurfaceMeta(
name=sumo_surface_meta.name,
is_observation=sumo_surface_meta.is_observation,
iso_date_or_interval=sumo_surface_meta.iso_date_or_interval,
value_min=sumo_surface_meta.zmin,
value_max=sumo_surface_meta.zmax,
name_is_stratigraphic_offical=True,
stratigraphic_feature=strat_surface.feature,
relative_stratigraphic_level=strat_surface.relative_strat_unit_level,
parent_stratigraphic_identifier=strat_surface.strat_unit_parent,
stratigraphic_identifier=strat_surface.strat_unit_identifier,
attribute_name=sumo_surface_meta.tagname,
attribute_type=schemas.SurfaceAttributeType(sumo_surface_meta.content.value),
)
surface_metas_with_official_strat_name.append(surface_meta)

# Append non-official strat names
for sumo_surface_meta in sumo_surface_metas:
if sumo_surface_meta.name not in [s.name for s in surface_metas_with_official_strat_name]:
surface_meta = schemas.SurfaceMeta(
name=sumo_surface_meta.name,
is_observation=sumo_surface_meta.is_observation,
iso_date_or_interval=sumo_surface_meta.iso_date_or_interval,
value_min=sumo_surface_meta.zmin,
value_max=sumo_surface_meta.zmax,
name_is_stratigraphic_offical=False,
stratigraphic_feature=None,
relative_stratigraphic_level=None,
parent_stratigraphic_identifier=None,
stratigraphic_identifier=None,
attribute_name=sumo_surface_meta.tagname,
attribute_type=schemas.SurfaceAttributeType(sumo_surface_meta.content.value),
api_meta_arr: list[schemas.SurfaceMeta] = []
all_sumo_surf_names: set[str] = set()
stratigraphic_sumo_surf_names: set[str] = set()
for sumo_surf in sumo_surf_meta_set.surfaces:
api_meta_arr.append(
schemas.SurfaceMeta(
name=sumo_surf.name,
name_is_stratigraphic_offical=sumo_surf.is_stratigraphic,
attribute_name=sumo_surf.attribute_name,
attribute_type=schemas.SurfaceAttributeType.from_sumo_content(sumo_surf.content),
time_type=schemas.SurfaceTimeType(sumo_surf.time_type.value),
is_observation=sumo_surf.is_observation,
value_min=sumo_surf.global_min_val,
value_max=sumo_surf.global_max_val,
)
)

all_sumo_surf_names.add(sumo_surf.name)
if sumo_surf.is_stratigraphic:
stratigraphic_sumo_surf_names.add(sumo_surf.name)

surface_metas_with_custom_names.append(surface_meta)
names_ordered_by_stratigraphy: list[str] = []
for strat_surf in ordered_stratigraphic_surfaces:
if strat_surf.name in stratigraphic_sumo_surf_names:
names_ordered_by_stratigraphy.append(strat_surf.name)

return surface_metas_with_official_strat_name + surface_metas_with_custom_names
remaining_sumo_surf_names: set[str] = all_sumo_surf_names.difference(names_ordered_by_stratigraphy)
remaining_names_sorted: list[str] = sorted(remaining_sumo_surf_names)

return schemas.SurfaceMetaSet(
surfaces=api_meta_arr,
time_points_iso_str=sumo_surf_meta_set.time_points_iso_str,
time_intervals_iso_str=sumo_surf_meta_set.time_intervals_iso_str,
surface_names_in_strat_order=names_ordered_by_stratigraphy + remaining_names_sorted,
)


def from_api_cumulative_length_polyline_to_xtgeo_polyline(
Expand Down
150 changes: 115 additions & 35 deletions backend_py/primary/primary/routers/surface/router.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import asyncio
import logging
from typing import List, Union, Optional
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Response, Body
from webviz_pkg.core_utils.perf_timer import PerfTimer
from webviz_pkg.core_utils.perf_metrics import PerfMetrics

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
from primary.services.smda_access.stratigraphy_access import StratigraphyAccess, 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.utils.statistic_function import StatisticFunction
Expand All @@ -26,32 +28,69 @@
router = APIRouter()


@router.get("/surface_directory/")
async def get_surface_directory(
@router.get("/realization_surfaces_metadata/")
async def get_realization_surfaces_metadata(
response: Response,
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
) -> List[schemas.SurfaceMeta]:
) -> schemas.SurfaceMetaSet:
"""
Get a directory of surfaces in a Sumo ensemble
Get metadata for realization surfaces in a Sumo ensemble
"""
surface_access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
sumo_surf_dir = await surface_access.get_surface_directory_async()
perf_metrics = ResponsePerfMetrics(response)

case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid)
strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async()
strat_access: Union[StratigraphyAccess, _mocked_stratigraphy_access.StratigraphyAccess]
async with asyncio.TaskGroup() as tg:
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
surf_meta_task = tg.create_task(access.get_realization_surfaces_metadata_async())
surf_meta_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-meta"))

strat_units_task = tg.create_task(_get_stratigraphic_units_for_case_async(authenticated_user, case_uuid))
strat_units_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-strat"))

perf_metrics.reset_lap_timer()
sumo_surf_meta_set = surf_meta_task.result()
strat_units = strat_units_task.result()

sorted_stratigraphic_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units)
api_surf_meta_set = converters.to_api_surface_meta_set(sumo_surf_meta_set, sorted_stratigraphic_surfaces)
perf_metrics.record_lap("compose")

LOGGER.info(f"Got metadata for realization surfaces in: {perf_metrics.to_string()}")

return api_surf_meta_set


@router.get("/observed_surfaces_metadata/")
async def get_observed_surfaces_metadata(
response: Response,
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
) -> schemas.SurfaceMetaSet:
"""
Get metadata for observed surfaces in a Sumo case
"""
perf_metrics = ResponsePerfMetrics(response)

async with asyncio.TaskGroup() as tg:
access = SurfaceAccess.from_case_uuid_no_iteration(authenticated_user.get_sumo_access_token(), case_uuid)
surf_meta_task = tg.create_task(access.get_observed_surfaces_metadata_async())
surf_meta_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-meta"))

strat_units_task = tg.create_task(_get_stratigraphic_units_for_case_async(authenticated_user, case_uuid))
strat_units_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-strat"))

perf_metrics.reset_lap_timer()
sumo_surf_meta_set = surf_meta_task.result()
strat_units = strat_units_task.result()

if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN":
strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token())
else:
strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token())
strat_units = await strat_access.get_stratigraphic_units(strat_column_identifier)
sorted_stratigraphic_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units)
api_surf_meta_set = converters.to_api_surface_meta_set(sumo_surf_meta_set, sorted_stratigraphic_surfaces)
perf_metrics.record_lap("compose")

LOGGER.info(f"Got metadata for observed surfaces in: {perf_metrics.to_string()}")

return converters.to_api_surface_directory(sumo_surf_dir, sorted_stratigraphic_surfaces)
return api_surf_meta_set


@router.get("/realization_surface_data/")
Expand All @@ -67,9 +106,7 @@ async def get_realization_surface_data(
) -> schemas.SurfaceData:
perf_metrics = ResponsePerfMetrics(response)

access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
xtgeo_surf = await access.get_realization_surface_data_async(
real_num=realization_num, name=name, attribute=attribute, time_or_interval_str=time_or_interval
)
Expand All @@ -86,6 +123,34 @@ async def get_realization_surface_data(
return surf_data_response


@router.get("/observed_surface_data/")
async def get_observed_surface_data(
response: Response,
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
name: str = Query(description="Surface name"),
attribute: str = Query(description="Surface attribute"),
time_or_interval: str = Query(description="Time point or time interval string"),
) -> schemas.SurfaceData:
perf_metrics = ResponsePerfMetrics(response)

access = SurfaceAccess.from_case_uuid_no_iteration(authenticated_user.get_sumo_access_token(), case_uuid)
xtgeo_surf = await access.get_observed_surface_data_async(
name=name, attribute=attribute, time_or_interval_str=time_or_interval
)
perf_metrics.record_lap("get-surf")

if not xtgeo_surf:
raise HTTPException(status_code=404, detail="Surface not found")

surf_data_response = converters.to_api_surface_data(xtgeo_surf)
perf_metrics.record_lap("convert")

LOGGER.info(f"Loaded observed surface in: {perf_metrics.to_string()}")

return surf_data_response


@router.get("/statistical_surface_data/")
async def get_statistical_surface_data(
response: Response,
Expand All @@ -99,9 +164,7 @@ async def get_statistical_surface_data(
) -> schemas.SurfaceData:
perf_metrics = ResponsePerfMetrics(response)

access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)

service_stat_func_to_compute = StatisticFunction.from_string_value(statistic_function)
if service_stat_func_to_compute is None:
Expand Down Expand Up @@ -143,9 +206,7 @@ async def get_property_surface_resampled_to_static_surface(
) -> schemas.SurfaceData:
perf_metrics = ResponsePerfMetrics(response)

access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
xtgeo_surf_mesh = await access.get_realization_surface_data_async(
real_num=realization_num_mesh, name=name_mesh, attribute=attribute_mesh
)
Expand Down Expand Up @@ -188,9 +249,7 @@ async def get_property_surface_resampled_to_statistical_static_surface(
) -> schemas.SurfaceData:
timer = PerfTimer()

access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
service_stat_func_to_compute = StatisticFunction.from_string_value(statistic_function)
if service_stat_func_to_compute is not None:
xtgeo_surf_mesh = await access.get_statistical_surface_data_async(
Expand All @@ -217,7 +276,7 @@ async def get_property_surface_resampled_to_statistical_static_surface(
return surf_data_response


@router.post("get_surface_intersection")
@router.post("/get_surface_intersection")
async def post_get_surface_intersection(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
Expand All @@ -233,9 +292,7 @@ async def post_get_surface_intersection(
The surface intersection data for surface name contains: An array of z-points, i.e. one z-value/depth per (x, y)-point in polyline,
and cumulative lengths, the accumulated length at each z-point in the array.
"""
access = await SurfaceAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
access = SurfaceAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)

surface = await access.get_realization_surface_data_async(
real_num=realization_num, name=name, attribute=attribute, time_or_interval_str=time_or_interval_str
Expand Down Expand Up @@ -288,3 +345,26 @@ async def post_sample_surface_in_points(
)

return intersections


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)
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
if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN":
strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token())
else:
strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token())

strat_units = await strat_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()}")

return strat_units
Loading

0 comments on commit 3be75fe

Please sign in to comment.