Skip to content

Commit

Permalink
Add SeismicAccess (equinor#364)
Browse files Browse the repository at this point in the history
  • Loading branch information
HansKallekleiv authored Sep 27, 2023
1 parent 1c2144d commit 290273a
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 0 deletions.
2 changes: 2 additions & 0 deletions backend/src/backend/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .routers.pvt.router import router as pvt_router
from .routers.well_completions.router import router as well_completions_router
from .routers.well.router import router as well_router
from .routers.seismic.router import router as seismic_router
from .routers.surface_polygons.router import router as surface_polygons_router

logging.basicConfig(
Expand Down Expand Up @@ -55,6 +56,7 @@ def custom_generate_unique_id(route: APIRoute) -> str:
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"])
app.include_router(seismic_router, prefix="/seismic", tags=["seismic"])
app.include_router(surface_polygons_router, prefix="/surface_polygons", tags=["surface_polygons"])

authHelper = AuthHelper()
Expand Down
Empty file.
31 changes: 31 additions & 0 deletions backend/src/backend/primary/routers/seismic/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging
from typing import List

from fastapi import APIRouter, Depends, HTTPException, Query

from src.services.sumo_access.seismic_access import SeismicAccess
from src.services.utils.authenticated_user import AuthenticatedUser
from src.backend.auth.auth_helper import AuthHelper

from . import schemas

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/seismic_directory/")
def get_seismic_directory(
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.SeismicCubeMeta]:
"""
Get a directory of seismic cubes.
"""
access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
seismic_cube_metas = access.get_seismic_directory()
try:
return [schemas.SeismicCubeMeta(**meta.__dict__) for meta in seismic_cube_metas]
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
13 changes: 13 additions & 0 deletions backend/src/backend/primary/routers/seismic/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pydantic import BaseModel


class SeismicCubeMeta(BaseModel):
seismic_attribute: str
iso_date_or_interval: str
is_observation: bool
is_depth: bool


class VdsHandle(BaseModel):
sas_token: str
vds_url: str
120 changes: 120 additions & 0 deletions backend/src/services/sumo_access/seismic_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import logging

from typing import List, Optional

from fmu.sumo.explorer import TimeFilter, TimeType
from fmu.sumo.explorer.objects import Case, CaseCollection
from fmu.sumo.explorer.objects.cube_collection import CubeCollection
from sumo.wrapper import SumoClient

from ._helpers import create_sumo_client_instance
from .seismic_types import SeismicCubeMeta, VdsHandle

LOGGER = logging.getLogger(__name__)


class SeismicAccess:
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_sumo_case(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 get_seismic_directory(self) -> List[SeismicCubeMeta]:
case = self._get_sumo_case()

seismic_cube_collection: CubeCollection = case.cubes.filter(iteration=self._iteration_name, realization=0)
seismic_cube_metas: List[SeismicCubeMeta] = []
for cube in seismic_cube_collection:
t_start = cube["data"].get("time", {}).get("t0", {}).get("value", None)
t_end = cube["data"].get("time", {}).get("t1", {}).get("value", None)

if not t_start and not t_end:
raise ValueError(f"Cube {cube['data']['tagname']} has no time information")

if t_start and not t_end:
iso_string_or_time_interval = t_start

else:
iso_string_or_time_interval = f"{t_start}/{t_end}"

seismic_meta = SeismicCubeMeta(
seismic_attribute=cube["data"].get("tagname"),
iso_date_or_interval=iso_string_or_time_interval,
is_observation=cube["data"]["is_observation"],
is_depth=cube["data"]["vertical_domain"] == "depth",
)
seismic_cube_metas.append(seismic_meta)
return seismic_cube_metas

def get_vds_handle(
self,
seismic_attribute: str,
realization: int,
time_or_interval_str: str,
observed: bool = False,
) -> VdsHandle:
"""Get the vds handle for a given cube"""
case = self._get_sumo_case()
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,
)

cube_collection: CubeCollection = case.cubes.filter(
tagname=seismic_attribute,
realization=realization,
iteration=self._iteration_name,
time=time_filter,
# is_observation=observed, # Does not work for observed. Only handles observed on case level?
)

# Filter on observed
cubes = []
for cube in cube_collection:
if cube["data"]["is_observation"] == observed:
cubes.append(cube)
break

if not cubes:
raise ValueError(f"Cube {seismic_attribute} not found in case {self._case_uuid}")
if len(cubes) > 1:
raise ValueError(f"Multiple cubes found for {seismic_attribute} in case {self._case_uuid}")
cube = cubes[0]

return VdsHandle(
sas_token=cube.sas,
vds_url=clean_vds_url(cube.url),
)


def clean_vds_url(vds_url: str) -> str:
"""clean vds url"""
return vds_url.replace(":443", "")
13 changes: 13 additions & 0 deletions backend/src/services/sumo_access/seismic_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pydantic import BaseModel


class SeismicCubeMeta(BaseModel):
seismic_attribute: str
iso_date_or_interval: str
is_observation: bool
is_depth: bool


class VdsHandle(BaseModel):
sas_token: str
vds_url: str
3 changes: 3 additions & 0 deletions frontend/src/api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GridService } from './services/GridService';
import { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
import { ParametersService } from './services/ParametersService';
import { PvtService } from './services/PvtService';
import { SeismicService } from './services/SeismicService';
import { SurfaceService } from './services/SurfaceService';
import { SurfacePolygonsService } from './services/SurfacePolygonsService';
import { TimeseriesService } from './services/TimeseriesService';
Expand All @@ -27,6 +28,7 @@ export class ApiService {
public readonly inplaceVolumetrics: InplaceVolumetricsService;
public readonly parameters: ParametersService;
public readonly pvt: PvtService;
public readonly seismic: SeismicService;
public readonly surface: SurfaceService;
public readonly surfacePolygons: SurfacePolygonsService;
public readonly timeseries: TimeseriesService;
Expand Down Expand Up @@ -54,6 +56,7 @@ export class ApiService {
this.inplaceVolumetrics = new InplaceVolumetricsService(this.request);
this.parameters = new ParametersService(this.request);
this.pvt = new PvtService(this.request);
this.seismic = new SeismicService(this.request);
this.surface = new SurfaceService(this.request);
this.surfacePolygons = new SurfacePolygonsService(this.request);
this.timeseries = new TimeseriesService(this.request);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type { InplaceVolumetricsCategoricalMetaData as InplaceVolumetricsCategor
export type { InplaceVolumetricsTableMetaData as InplaceVolumetricsTableMetaData_api } from './models/InplaceVolumetricsTableMetaData';
export type { PolygonData as PolygonData_api } from './models/PolygonData';
export type { PvtData as PvtData_api } from './models/PvtData';
export type { SeismicCubeMeta as SeismicCubeMeta_api } from './models/SeismicCubeMeta';
export { SensitivityType as SensitivityType_api } from './models/SensitivityType';
export { StatisticFunction as StatisticFunction_api } from './models/StatisticFunction';
export type { StatisticValueObject as StatisticValueObject_api } from './models/StatisticValueObject';
Expand Down Expand Up @@ -59,6 +60,7 @@ export { GridService } from './services/GridService';
export { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
export { ParametersService } from './services/ParametersService';
export { PvtService } from './services/PvtService';
export { SeismicService } from './services/SeismicService';
export { SurfaceService } from './services/SurfaceService';
export { SurfacePolygonsService } from './services/SurfacePolygonsService';
export { TimeseriesService } from './services/TimeseriesService';
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/api/models/SeismicCubeMeta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type SeismicCubeMeta = {
seismic_attribute: string;
iso_date_or_interval: string;
is_observation: boolean;
is_depth: boolean;
};

38 changes: 38 additions & 0 deletions frontend/src/api/services/SeismicService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { SeismicCubeMeta } from '../models/SeismicCubeMeta';

import type { CancelablePromise } from '../core/CancelablePromise';
import type { BaseHttpRequest } from '../core/BaseHttpRequest';

export class SeismicService {

constructor(public readonly httpRequest: BaseHttpRequest) {}

/**
* Get Seismic Directory
* Get a directory of seismic cubes.
* @param caseUuid Sumo case uuid
* @param ensembleName Ensemble name
* @returns SeismicCubeMeta Successful Response
* @throws ApiError
*/
public getSeismicDirectory(
caseUuid: string,
ensembleName: string,
): CancelablePromise<Array<SeismicCubeMeta>> {
return this.httpRequest.request({
method: 'GET',
url: '/seismic/seismic_directory/',
query: {
'case_uuid': caseUuid,
'ensemble_name': ensembleName,
},
errors: {
422: `Validation Error`,
},
});
}

}

0 comments on commit 290273a

Please sign in to comment.