Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map Matrix #472

Closed
wants to merge 14 commits into from
Closed
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
16 changes: 0 additions & 16 deletions backend/.vscode/launch.json

This file was deleted.

31 changes: 30 additions & 1 deletion backend/src/backend/primary/routers/surface/converters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List
import base64

import numpy as np
import xtgeo
Expand All @@ -9,6 +10,8 @@
from src.services.sumo_access.surface_types import XtgeoSurfaceIntersectionPolyline, XtgeoSurfaceIntersectionResult
from src.services.utils.b64 import b64_encode_float_array_as_float32
from src.services.utils.surface_to_float32 import surface_to_float32_numpy_array
from src.services.utils.surface_to_png import surface_to_png_bytes_optimized
from src.services.utils.surface_orientation import calc_surface_orientation_for_colormap_layer

from . import schemas

Expand All @@ -26,7 +29,7 @@ def resample_property_surface_to_mesh_surface(
return mesh_surface


def to_api_surface_data(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData:
def to_api_surface_data_as_float32(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData:
"""
Create API SurfaceData from xtgeo regular surface
"""
Expand All @@ -52,6 +55,32 @@ def to_api_surface_data(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData
)


def to_api_surface_data_as_png(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceDataPng:
"""
Create API SurfaceData from xtgeo regular surface
"""

png_bytes: bytes = surface_to_png_bytes_optimized(xtgeo_surf)
base64_data = base64.b64encode(png_bytes).decode("ascii")

surf_orient = calc_surface_orientation_for_colormap_layer(xtgeo_surf)

return schemas.SurfaceDataPng(
x_min_surf_orient=surf_orient.x_min,
x_max_surf_orient=surf_orient.x_max,
y_min_surf_orient=surf_orient.y_min,
y_max_surf_orient=surf_orient.y_max,
x_min=xtgeo_surf.xmin,
x_max=xtgeo_surf.xmax,
y_min=xtgeo_surf.ymin,
y_max=xtgeo_surf.ymax,
val_min=xtgeo_surf.values.min(),
val_max=xtgeo_surf.values.max(),
rot_deg=surf_orient.rot_around_xmin_ymax_deg,
base64_encoded_image=f"{base64_data}",
)


def to_api_surface_directory(
sumo_surface_dir: List[SumoSurfaceMeta], stratigraphical_names: List[StratigraphicSurface]
) -> List[schemas.SurfaceMeta]:
Expand Down
126 changes: 119 additions & 7 deletions backend/src/backend/primary/routers/surface/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@

from src.services.sumo_access.surface_access import SurfaceAccess
from src.services.smda_access.stratigraphy_access import StratigraphyAccess
from src.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy
from src.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access
from src.services.smda_access.stratigraphy_utils import (
sort_stratigraphic_names_by_hierarchy,
)
from src.services.smda_access.mocked_drogon_smda_access import (
_mocked_stratigraphy_access,
)
from src.services.utils.statistic_function import StatisticFunction
from src.services.utils.authenticated_user import AuthenticatedUser
from src.services.utils.perf_timer import PerfTimer
Expand Down Expand Up @@ -68,14 +72,17 @@ async def get_realization_surface_data(

access = await 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
real_num=realization_num,
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)
surf_data_response = converters.to_api_surface_data_as_float32(xtgeo_surf)
perf_metrics.record_lap("convert")

LOGGER.info(f"Loaded realization surface in: {perf_metrics.to_string()}")
Expand Down Expand Up @@ -113,14 +120,119 @@ async def get_statistical_surface_data(
if not xtgeo_surf:
raise HTTPException(status_code=404, detail="Could not find or compute surface")

surf_data_response: schemas.SurfaceData = converters.to_api_surface_data(xtgeo_surf)
surf_data_response: schemas.SurfaceData = converters.to_api_surface_data_as_float32(xtgeo_surf)
perf_metrics.record_lap("convert")

LOGGER.debug(f"Calculated statistical surface in: {perf_metrics.to_string()}")

return surf_data_response


@router.get("/realization_surface_data_as_png/")
async def get_realization_surface_data_as_png(
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"),
realization_num: int = Query(description="Realization number"),
name: str = Query(description="Surface name"),
attribute: str = Query(description="Surface attribute"),
time_or_interval: Optional[str] = Query(None, description="Time point or time interval string"),
) -> schemas.SurfaceDataPng:
perf_metrics = PerfMetrics(response)

access = await 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,
)
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_as_png(xtgeo_surf)
perf_metrics.record_lap("convert")

LOGGER.debug(f"Loaded realization surface in: {perf_metrics.to_string()}")

return surf_data_response


@router.get("/statistical_surface_data_as_png/")
async def get_statistical_surface_data_as_png(
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"),
statistic_function: schemas.SurfaceStatisticFunction = Query(description="Statistics to calculate"),
realization_nums: Optional[List[int]] = Query(None, description="Realization numbers"),
name: str = Query(description="Surface name"),
attribute: str = Query(description="Surface attribute"),
time_or_interval: Optional[str] = Query(None, description="Time point or time interval string"),
) -> schemas.SurfaceDataPng:
perf_metrics = PerfMetrics(response)

access = await 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:
raise HTTPException(status_code=404, detail="Invalid statistic requested")

xtgeo_surf = await access.get_statistical_surface_data_async(
statistic_function=service_stat_func_to_compute,
name=name,
attribute=attribute,
time_or_interval_str=time_or_interval,
realization_nums=realization_nums,
)
perf_metrics.record_lap("sumo-calc")

if not xtgeo_surf:
raise HTTPException(status_code=404, detail="Could not find or compute surface")

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

LOGGER.info(f"Calculated statistical surface in: {perf_metrics.to_string()}")

return surf_data_response


@router.get("/observation_surface_data_as_png/")
async def get_observation_surface_data_as_png(
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"),
name: str = Query(description="Surface name"),
attribute: str = Query(description="Surface attribute"),
time_or_interval: Optional[str] = Query(None, description="Time point or time interval string"),
) -> schemas.SurfaceDataPng:
perf_metrics = PerfMetrics(response)

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

xtgeo_surf = await access.get_observation_surface_data_async(
name=name,
attribute=attribute,
time_or_interval_str=time_or_interval,
)
perf_metrics.record_lap("sumo-calc")

if not xtgeo_surf:
raise HTTPException(status_code=404, detail="Could not find or compute surface")

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

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

return surf_data_response


# pylint: disable=too-many-arguments
@router.get("/property_surface_resampled_to_static_surface/")
async def get_property_surface_resampled_to_static_surface(
Expand Down Expand Up @@ -158,7 +270,7 @@ async def get_property_surface_resampled_to_static_surface(
resampled_surface = converters.resample_property_surface_to_mesh_surface(xtgeo_surf_mesh, xtgeo_surf_property)
perf_metrics.record_lap("resample")

surf_data_response: schemas.SurfaceData = converters.to_api_surface_data(resampled_surface)
surf_data_response: schemas.SurfaceData = converters.to_api_surface_data_as_float32(resampled_surface)
perf_metrics.record_lap("convert")

LOGGER.info(f"Loaded property surface in: {perf_metrics.to_string()}")
Expand Down Expand Up @@ -201,7 +313,7 @@ async def get_property_surface_resampled_to_statistical_static_surface(

resampled_surface = converters.resample_property_surface_to_mesh_surface(xtgeo_surf_mesh, xtgeo_surf_property)

surf_data_response = converters.to_api_surface_data(resampled_surface)
surf_data_response = converters.to_api_surface_data_as_float32(resampled_surface)

LOGGER.info(f"Loaded property surface and created image, total time: {timer.elapsed_ms()}ms")

Expand Down
15 changes: 15 additions & 0 deletions backend/src/backend/primary/routers/surface/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,18 @@ class SurfaceRealizationSampleValues(BaseModel):
class PointSetXY(BaseModel):
x_points: list[float]
y_points: list[float]


class SurfaceDataPng(BaseModel):
x_min_surf_orient: float
x_max_surf_orient: float
y_min_surf_orient: float
y_max_surf_orient: float
x_min: float
x_max: float
y_min: float
y_max: float
val_min: float
val_max: float
rot_deg: float
base64_encoded_image: str
59 changes: 58 additions & 1 deletion backend/src/services/sumo_access/surface_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ async def get_realization_surface_intersection_async(
return _make_intersection(surface, xtgeo_fencespec)

async def get_realization_surface_data_async(
self, real_num: int, name: str, attribute: str, time_or_interval_str: Optional[str] = None
self,
real_num: int,
name: str,
attribute: str,
time_or_interval_str: Optional[str] = None,
) -> Optional[xtgeo.RegularSurface]:
"""
Get surface data for a realization surface
Expand Down Expand Up @@ -169,12 +173,64 @@ async def get_realization_surface_data_async(

return xtgeo_surf

async def get_observation_surface_data_async(
self,
name: str,
attribute: str,
time_or_interval_str: Optional[str] = None,
) -> Optional[xtgeo.RegularSurface]:
"""
Get surface data for an observed surface
"""

if time_or_interval_str is None:
time_filter = TimeFilter(TimeType.NONE)

else:
timestamp_arr = time_or_interval_str.split("/", 1)
if len(timestamp_arr) == 0 or len(timestamp_arr) > 2:
raise ValueError("time_or_interval_str must contain a single timestamp or interval")
if len(timestamp_arr) == 1:
time_filter = TimeFilter(
TimeType.TIMESTAMP,
start=timestamp_arr[0],
end=timestamp_arr[0],
exact=True,
)
else:
time_filter = TimeFilter(
TimeType.INTERVAL,
start=timestamp_arr[0],
end=timestamp_arr[1],
exact=True,
)
# Remove this once Sumo enforces tagname (tagname-unset)
# https://github.com/equinor/webviz/issues/433
tagname = attribute if attribute != "Unknown" else ""
surface_collection = self._case.surfaces.filter(
stage="case", # Should get these from dataio,
name=name,
tagname=tagname,
time=time_filter,
)

surf_count = await surface_collection.length_async()
if surf_count == 0:
return None

sumo_surf: Surface = await surface_collection.getitem_async(0)
byte_stream: BytesIO = await sumo_surf.blob_async
xtgeo_surf = xtgeo.surface_from_file(byte_stream)

return xtgeo_surf

async def get_statistical_surface_data_async(
self,
statistic_function: StatisticFunction,
name: str,
attribute: str,
time_or_interval_str: Optional[str] = None,
realization_nums: Optional[List[int]] = None,
) -> Optional[xtgeo.RegularSurface]:
"""
Compute statistic and return surface data
Expand Down Expand Up @@ -210,6 +266,7 @@ async def get_statistical_surface_data_async(
name=name,
tagname=attribute,
time=time_filter,
realization=realization_nums,
)

surf_count = await surface_collection.length_async()
Expand Down
Loading
Loading