-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e7f5d6c
commit dd1f2e0
Showing
27 changed files
with
1,337 additions
and
227 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
23 changes: 23 additions & 0 deletions
23
backend/src/backend/primary/routers/surface_polygons/converters.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from typing import List | ||
import orjson | ||
import xtgeo | ||
|
||
from src.services.utils.surface_to_float32 import surface_to_float32_array | ||
from . import schemas | ||
|
||
|
||
def to_api_polygons_data(xtgeo_poly: xtgeo.Polygons) -> List[schemas.PolygonData]: | ||
""" | ||
Create API SurfaceData from xtgeo regular surface | ||
""" | ||
polydata: List[schemas.PolygonData] = [] | ||
for poly_id, polygon in xtgeo_poly.dataframe.groupby("POLY_ID"): | ||
polydata.append( | ||
schemas.PolygonData( | ||
x_arr=list(polygon.X_UTME), | ||
y_arr=list(polygon.Y_UTMN), | ||
z_arr=list(polygon.Z_TVDSS), | ||
poly_id=poly_id, | ||
) | ||
) | ||
return polydata |
57 changes: 57 additions & 0 deletions
57
backend/src/backend/primary/routers/surface_polygons/router.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import logging | ||
from typing import List | ||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query | ||
|
||
from src.services.sumo_access.surface_polygon_access import SurfacePolygonsAccess | ||
from src.services.utils.statistic_function import StatisticFunction | ||
from src.services.utils.authenticated_user import AuthenticatedUser | ||
from src.services.utils.perf_timer import PerfTimer | ||
from src.backend.auth.auth_helper import AuthHelper | ||
|
||
from src.services.sumo_access.generic_types import SumoContent | ||
|
||
from . import schemas | ||
from . import converters | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/surface_polygons_directory/") | ||
def get_surface_polygons_directory( | ||
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), | ||
case_uuid: str = Query(description="Sumo case uuid"), | ||
ensemble_name: str = Query(description="Ensemble name"), | ||
) -> schemas.SurfacePolygonDirectory: | ||
""" | ||
Get a directory of surface polygon names and attributes | ||
""" | ||
access = SurfacePolygonsAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) | ||
polygons_dir = access.get_surface_polygons_dir() | ||
|
||
return polygons_dir | ||
|
||
|
||
@router.get("/surface_polygons_data/") | ||
def get_surface_polygons_data( | ||
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"), | ||
) -> List[schemas.PolygonData]: | ||
timer = PerfTimer() | ||
|
||
access = SurfacePolygonsAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) | ||
xtgeo_poly = access.get_surface_polygons(real_num=realization_num, name=name, attribute=attribute) | ||
|
||
if not xtgeo_poly: | ||
raise HTTPException(status_code=404, detail="Surface not found") | ||
|
||
poly_data_response = converters.to_api_polygons_data(xtgeo_poly) | ||
|
||
LOGGER.debug(f"Loaded static surface and created image, total time: {timer.elapsed_ms()}ms") | ||
return poly_data_response |
17 changes: 17 additions & 0 deletions
17
backend/src/backend/primary/routers/surface_polygons/schemas.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from enum import Enum | ||
from typing import List | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class SurfacePolygonDirectory(BaseModel): | ||
names: List[str] | ||
attributes: List[str] | ||
valid_attributes_for_name: List[List[int]] | ||
|
||
|
||
class PolygonData(BaseModel): | ||
x_arr: List[float] | ||
y_arr: List[float] | ||
z_arr: List[float] | ||
poly_id: int | str |
153 changes: 153 additions & 0 deletions
153
backend/src/services/sumo_access/surface_polygon_access.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import logging | ||
from io import BytesIO | ||
from typing import List, Optional, Tuple | ||
|
||
import pandas as pd | ||
import xtgeo | ||
from fmu.sumo.explorer import TimeFilter, TimeType | ||
from fmu.sumo.explorer.objects import Case, CaseCollection, SurfaceCollection, PolygonsCollection | ||
from sumo.wrapper import SumoClient | ||
|
||
from src.services.utils.perf_timer import PerfTimer | ||
from src.services.utils.statistic_function import StatisticFunction | ||
|
||
from ._helpers import create_sumo_client_instance | ||
from .surface_polygon_types import SurfacePolygonsDirectory | ||
from .generic_types import SumoContent | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class SurfacePolygonsAccess: | ||
def __init__(self, access_token: str, case_uuid: str, iteration_name: str): | ||
self._sumo_client: SumoClient = create_sumo_client_instance(access_token) | ||
self._case_uuid = case_uuid | ||
self._iteration_name = iteration_name | ||
self._sumo_case_obj: Optional[Case] = None | ||
|
||
def get_surface_polygons_dir(self, content_filter: Optional[List[SumoContent]] = None) -> SurfacePolygonsDirectory: | ||
""" | ||
Get a directory of surface polygon names and attributes. | ||
""" | ||
timer = PerfTimer() | ||
|
||
LOGGER.debug("Getting data for surface polygon directory...") | ||
|
||
case = self._get_my_sumo_case_obj() | ||
|
||
polygons_collection: PolygonsCollection = case.polygons.filter( | ||
iteration=self._iteration_name, | ||
realization=0, | ||
) | ||
|
||
names = sorted(polygons_collection.names) | ||
attributes = sorted(polygons_collection.tagnames) | ||
|
||
if content_filter is not None: | ||
if not any([SumoContent.has(content) for content in content_filter]): | ||
raise ValueError(f"Invalid content filter: {content_filter}") | ||
polygons_with_filtered_content = [ | ||
surf for surf in polygons_collection if surf["data"]["content"] in content_filter | ||
] | ||
for surf in polygons_collection: | ||
if surf["data"]["content"] in content_filter: | ||
print(surf["data"]["content"]) | ||
names = sorted(list(set([surf.name for surf in polygons_with_filtered_content]))) | ||
attributes = sorted(list(set([surf.tagname for surf in polygons_with_filtered_content]))) | ||
|
||
else: | ||
names = sorted(polygons_collection.names) | ||
attributes = sorted(polygons_collection.tagnames) | ||
|
||
LOGGER.debug( | ||
f"Build valid name/attribute combinations for surface polygons directory " | ||
f"(num names={len(names)}, num attributes={len(attributes)})..." | ||
) | ||
|
||
valid_attributes_for_name: List[List[int]] = [] | ||
|
||
for name in names: | ||
filtered_coll = polygons_collection.filter(name=name) | ||
|
||
filtered_attributes = [tagname for tagname in filtered_coll.tagnames if tagname in attributes] | ||
attribute_indices: List[int] = [] | ||
for attr in filtered_attributes: | ||
attr_idx = attributes.index(attr) | ||
attribute_indices.append(attr_idx) | ||
|
||
valid_attributes_for_name.append(attribute_indices) | ||
|
||
polygon_dir = SurfacePolygonsDirectory( | ||
names=names, | ||
attributes=attributes, | ||
valid_attributes_for_name=valid_attributes_for_name, | ||
) | ||
|
||
LOGGER.debug(f"Downloaded and built surface polygon directory in: {timer.elapsed_ms():}ms") | ||
|
||
return polygon_dir | ||
|
||
def get_surface_polygons(self, real_num: int, name: str, attribute: str) -> Optional[xtgeo.Polygons]: | ||
""" | ||
Get polygons data | ||
""" | ||
timer = PerfTimer() | ||
addr_str = self._make_addr_str(real_num, name, attribute, None) | ||
|
||
case = self._get_my_sumo_case_obj() | ||
|
||
polygons_collection: PolygonsCollection = case.polygons.filter( | ||
iteration=self._iteration_name, | ||
realization=real_num, | ||
name=name, | ||
tagname=attribute, | ||
) | ||
|
||
surface_polygons_count = len(polygons_collection) | ||
if surface_polygons_count == 0: | ||
LOGGER.warning(f"No surface polygons found in Sumo for {addr_str}") | ||
return None | ||
|
||
is_valid = False | ||
if surface_polygons_count > 1: | ||
LOGGER.warning( | ||
f"Multiple ({surface_polygons_count}) polygons set found in Sumo for: {addr_str}. Returning first polygons set." | ||
) | ||
# HACK HACK HACK Some fields has multiple polygons sets for some reason... Unknown which one works | ||
for poly in polygons_collection: | ||
byte_stream: BytesIO = poly.blob | ||
poly_df = pd.read_csv(byte_stream) | ||
is_valid = set(["X_UTME", "Y_UTMN", "Z_TVDSS", "POLY_ID"]) == set(poly_df.columns) | ||
if is_valid: | ||
break | ||
else: | ||
sumo_polys = polygons_collection[0] | ||
byte_stream: BytesIO = sumo_polys.blob | ||
poly_df = pd.read_csv(byte_stream) | ||
is_valid = set(["X_UTME", "Y_UTMN", "Z_TVDSS", "POLY_ID"]) == set(poly_df.columns) | ||
if not is_valid: | ||
LOGGER.warning(f"Invalid surface polygons found in Sumo for {addr_str}") | ||
return None | ||
xtgeo_polygons = xtgeo.Polygons(poly_df) | ||
|
||
LOGGER.debug(f"Got surface polygons from Sumo in: {timer.elapsed_ms()}ms ({addr_str})") | ||
|
||
return xtgeo_polygons | ||
|
||
def _get_my_sumo_case_obj(self) -> Case: | ||
""" | ||
Get the Sumo case that we should be working on. | ||
Raises exception if case isn't found | ||
""" | ||
if self._sumo_case_obj is None: | ||
case_collection = CaseCollection(self._sumo_client).filter(uuid=self._case_uuid) | ||
if len(case_collection) != 1: | ||
raise ValueError(f"None or multiple sumo cases found {self._case_uuid=}") | ||
|
||
self._sumo_case_obj = case_collection[0] | ||
|
||
return self._sumo_case_obj | ||
|
||
def _make_addr_str(self, real_num: int, name: str, attribute: str, date_str: Optional[str]) -> str: | ||
addr_str = f"R:{real_num}__N:{name}__A:{attribute}__D:{date_str}__I:{self._iteration_name}__C:{self._case_uuid}" | ||
return addr_str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from __future__ import annotations | ||
|
||
from typing import List | ||
from enum import Enum | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class SurfacePolygonsDirectory(BaseModel): | ||
names: List[str] | ||
attributes: List[str] | ||
valid_attributes_for_name: List[List[int]] | ||
|
||
@classmethod | ||
def create_empty(cls) -> SurfacePolygonsDirectory: | ||
return cls(attributes=[], names=[]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.