From a61dd5233fc1316542927072e3df6165b0ad64ba Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:49:44 +0200 Subject: [PATCH 01/35] Vds Access --- backend/src/services/vds_access/__init__.py | 0 backend/src/services/vds_access/types.py | 41 ++++++ backend/src/services/vds_access/vds_access.py | 117 ++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 backend/src/services/vds_access/__init__.py create mode 100644 backend/src/services/vds_access/types.py create mode 100644 backend/src/services/vds_access/vds_access.py diff --git a/backend/src/services/vds_access/__init__.py b/backend/src/services/vds_access/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/services/vds_access/types.py b/backend/src/services/vds_access/types.py new file mode 100644 index 000000000..1127a4183 --- /dev/null +++ b/backend/src/services/vds_access/types.py @@ -0,0 +1,41 @@ +from typing import List + +from pydantic import BaseModel + + +class VdsHandle(BaseModel): + sas_token: str + vds_url: str + + +class VdsAxis(BaseModel): + annotation: str + max: float + min: float + samples: int + unit: str + + +class VdsBoundingBox(BaseModel): + cdp: List[List[float]] + ij: List[List[float]] + ilxl: List[List[float]] + + +class VdsMetaData(BaseModel): + axis: List[VdsAxis] + boundingBox: VdsBoundingBox + crs: str + + +class VdsSliceResponse(BaseModel): + values: List[List[float]] + geospatial: List[List[float]] + shape: List[int] + x: VdsAxis + y: VdsAxis + + +class VdsFenceResponse(BaseModel): + values: List[List[float]] + shape: List[int] diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py new file mode 100644 index 000000000..1aa6dc6cb --- /dev/null +++ b/backend/src/services/vds_access/vds_access.py @@ -0,0 +1,117 @@ +import logging +import os +from typing import List + +import numpy as np +import orjson as json +import requests +from requests_toolbelt.multipart.decoder import MultipartDecoder + +from .types import VdsFenceResponse, VdsHandle, VdsMetaData, VdsSliceResponse + +VDS_HOST_ADDRESS = os.getenv("VDS_HOST_ADDRESS") + +LOGGER = logging.getLogger(__name__) + + +class VdsAccess: + """Access to the service hosting vds-slice. + https://github.com/equinor/vds-slice""" + + def __init__(self, sumo_seismic_vds_handle: VdsHandle) -> None: + self.sas: str = sumo_seismic_vds_handle.sas_token + self.vds_url: str = sumo_seismic_vds_handle.vds_url + + def _query(self, endpoint: str, params: dict) -> requests.Response: + """Query the VDS service""" + params.update({"vds": self.vds_url, "sas": self.sas}) + response = requests.post( + f"{VDS_HOST_ADDRESS}/{endpoint}", + headers={"Content-Type": "application/json"}, + data=json.dumps(params), # pylint: disable=maybe-no-member + timeout=60, + ) + + if not response.ok: + raise RuntimeError(f"({str(response.status_code)})-{response.reason}-{response.text} ") + return response + + def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: + """Gets a slice in i,j,k direction from the VDS service""" + + endpoint = "slice" + params = { + "direction": direction, + "lineno": lineno, + "vds": self.vds_url, + "sas": self.sas, + } + response = self._query(endpoint, params) + + parts = MultipartDecoder.from_response(response).parts + meta = json.loads(parts[0].content) # pylint: disable=maybe-no-member + shape = (meta["y"]["samples"], meta["x"]["samples"]) + values = parts[1].content + values = np.ndarray(shape, "f4", values) + values = values.tolist() + return VdsSliceResponse(values=values, **meta) + + def get_fence(self, coordinates: List[List[float]], coordinate_system: str) -> VdsFenceResponse: + endpoint = "fence" + + params = { + "coordinateSystem": coordinate_system, + "coordinates": coordinates, + "vds": self.vds_url, + "sas": self.sas, + "interpolation": "linear", + "fillValue": 10, + } + response = self._query(endpoint, params) + + parts = MultipartDecoder.from_response(response).parts + metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member + values = parts[1].content + values = np.ndarray(metadata["shape"], metadata["format"], values) + values = values.tolist() + return VdsFenceResponse(values=values, shape=metadata["shape"]) + + def get_metadata(self) -> VdsMetaData: + endpoint = "metadata" + + params = { + "vds": self.vds_url, + "sas": self.sas, + } + response = self._query(endpoint, params) + if not response.ok: + raise RuntimeError(response.text) + metadata = response.json() + return VdsMetaData(**metadata) + + # def get_surface_values( + # self, + # xtgeo_surf: RegularSurface, + # above: float, + # below: float, + # attribute: str, + # fill_value: float = -999.25, + # ) -> np.ndarray: + # surface_values = xtgeo_surf.values.filled(fill_value) + # array_shape = surface_values.shape + # endpoint = "attributes/surface/along" + + # timer = PerfTimer() + + # params = { + # "surface": surface_values.tolist(), + # "above": above, + # "below": below, + # "attributes": [attribute], + # } + # response = self._query(endpoint, params) + # print(f"Got attribute surface from VDS in: {timer.elapsed_ms()}ms ({attribute})") + # parts = MultipartDecoder.from_response(response).parts + # seismic_values = np.ndarray(array_shape, "f4", parts[1].content) + # seismic_values = np.ma.masked_equal(seismic_values, fill_value) + # return seismic_values From 8ba2d275a73a538bfc04b369d0e6cd139bf6c568 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:56:24 +0200 Subject: [PATCH 02/35] Add env variable for vds service --- backend/src/services/vds_access/vds_access.py | 2 +- docker-compose-prod.yml | 1 + docker-compose.yml | 1 + radixconfig.yml | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 1aa6dc6cb..516c079e1 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -9,7 +9,7 @@ from .types import VdsFenceResponse, VdsHandle, VdsMetaData, VdsSliceResponse -VDS_HOST_ADDRESS = os.getenv("VDS_HOST_ADDRESS") +VDS_HOST_ADDRESS = os.getenv("WEBVIZ_VDS_HOST_ADDRESS") LOGGER = logging.getLogger(__name__) diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index f4a8a854a..9f237e6d9 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -25,6 +25,7 @@ services: - WEBVIZ_SMDA_RESOURCE_SCOPE - WEBVIZ_SMDA_SUBSCRIPTION_KEY - WEBVIZ_SUMO_ENV + - WEBVIZ_VDS_HOST_ADDRESS backend-user-session: build: diff --git a/docker-compose.yml b/docker-compose.yml index f6e4e1c56..dbc8f338a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,7 @@ services: - WEBVIZ_SMDA_RESOURCE_SCOPE - WEBVIZ_SMDA_SUBSCRIPTION_KEY - WEBVIZ_SUMO_ENV + - WEBVIZ_VDS_HOST_ADDRESS - CODESPACE_NAME # Automatically set env. variable by GitHub codespace volumes: - ./backend/src:/home/appuser/backend/src diff --git a/radixconfig.yml b/radixconfig.yml index 38f11200d..969d00104 100644 --- a/radixconfig.yml +++ b/radixconfig.yml @@ -32,6 +32,8 @@ spec: envVar: WEBVIZ_SMDA_RESOURCE_SCOPE - name: WEBVIZ-SMDA-SUBSCRIPTION-KEY envVar: WEBVIZ_SMDA_SUBSCRIPTION_KEY + - name: WEBVIZ-VDS-HOST-ADDRESS + envVar: WEBVIZ_VDS_HOST_ADDRESS variables: UVICORN_PORT: 5000 UVICORN_ENTRYPOINT: src.backend.primary.main:app From 0ac4585ace4051935ada832deb9195a74a01085c Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:08:08 +0200 Subject: [PATCH 03/35] Doc. split bytes=>numpy into separate function --- backend/src/services/vds_access/vds_access.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 516c079e1..2eba2c006 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -49,14 +49,15 @@ def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: response = self._query(endpoint, params) parts = MultipartDecoder.from_response(response).parts - meta = json.loads(parts[0].content) # pylint: disable=maybe-no-member - shape = (meta["y"]["samples"], meta["x"]["samples"]) - values = parts[1].content - values = np.ndarray(shape, "f4", values) - values = values.tolist() - return VdsSliceResponse(values=values, **meta) - - def get_fence(self, coordinates: List[List[float]], coordinate_system: str) -> VdsFenceResponse: + metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member + shape = (metadata["y"]["samples"], metadata["x"]["samples"]) + byte_array = parts[1].content + values_np = bytes_to_ndarray(byte_array, list(shape), metadata["format"]) + values = values_np.tolist() + return VdsSliceResponse(values=values, **metadata) + + def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cdp") -> VdsFenceResponse: + """Gets traces along an arbitrary path of x,y coordinates.""" endpoint = "fence" params = { @@ -71,12 +72,13 @@ def get_fence(self, coordinates: List[List[float]], coordinate_system: str) -> V parts = MultipartDecoder.from_response(response).parts metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member - values = parts[1].content - values = np.ndarray(metadata["shape"], metadata["format"], values) - values = values.tolist() + byte_array = parts[1].content + values_np = bytes_to_ndarray(byte_array, list(metadata["shape"]), metadata["format"]) + values = values_np.tolist() return VdsFenceResponse(values=values, shape=metadata["shape"]) def get_metadata(self) -> VdsMetaData: + """Gets metadata from the cube""" endpoint = "metadata" params = { @@ -115,3 +117,8 @@ def get_metadata(self) -> VdsMetaData: # seismic_values = np.ndarray(array_shape, "f4", parts[1].content) # seismic_values = np.ma.masked_equal(seismic_values, fill_value) # return seismic_values + + +def bytes_to_ndarray(bytes_data: bytes, shape: List[int], dtype: str) -> np.ndarray: + """Convert bytes to numpy ndarray""" + return np.ndarray(shape, dtype, bytes_data) From ae8421f770f9f18527412d5e39e9da0f33352e0b Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:24:55 +0200 Subject: [PATCH 04/35] Comment on fill value --- backend/src/services/vds_access/vds_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 2eba2c006..d8d3b3477 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -66,7 +66,7 @@ def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cd "vds": self.vds_url, "sas": self.sas, "interpolation": "linear", - "fillValue": 10, + "fillValue": -999, # Assumption is that this will fill out-of-bounds values, but it doesn't seem to work } response = self._query(endpoint, params) From 4c28062482eae00a5dff5836a4be6031d532b4bc Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sat, 30 Sep 2023 21:36:06 +0200 Subject: [PATCH 05/35] use add endpoints. use async --- .../backend/primary/routers/seismic/router.py | 62 ++++++++++++++++++- .../primary/routers/seismic/schemas.py | 7 +++ backend/src/services/vds_access/vds_access.py | 58 ++++++++++------- 3 files changed, 102 insertions(+), 25 deletions(-) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 8c902b7e2..d6c52a137 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -1,9 +1,12 @@ import logging from typing import List +import orjson as json -from fastapi import APIRouter, Depends, HTTPException, Query +import numpy as np +from fastapi import APIRouter, Depends, HTTPException, Query, Body from src.services.sumo_access.seismic_access import SeismicAccess +from src.services.vds_access.vds_access import VdsAccess from src.services.utils.authenticated_user import AuthenticatedUser from src.backend.auth.auth_helper import AuthHelper @@ -29,3 +32,60 @@ def get_seismic_directory( 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 + + +@router.post("/fence/") +async def get_fence( + 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"), + seismic_attribute: str = Query(description="Seismic cube attribute"), + time_or_interval_str: str = Query(description="Timestamp or timestep"), + observed: bool = Query(description="Observed or simulated"), + # cutting_plane: schemas.CuttingPlane = Body(alias="cuttingPlane", embed=True), +) -> schemas.SeismicIntersectionData: + seismic_access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + + try: + vds_handle = seismic_access.get_vds_handle( + realization=realization_num, + seismic_attribute=seismic_attribute, + time_or_interval_str=time_or_interval_str, + observed=observed, + ) + except ValueError as err: + raise HTTPException(status_code=404, detail=str(err)) from err + + vdsaccess = VdsAccess(vds_handle) + + vals = await vdsaccess.get_fence( + coordinate_system="cdp", + coordinates=[ + [x, y] + for x, y in zip( + [ + 463156.911, + 463564.402, + 463637.925, + 463690.658, + 463910.452, + ], + [ + 5929542.294, + 5931057.803, + 5931184.235, + 5931278.837, + 5931688.122, + ], + ) + ], + ) + meta = await vdsaccess.get_metadata() + z_axis_meta = meta.axis[2] + + z_arr = np.linspace(z_axis_meta.min, z_axis_meta.max, z_axis_meta.samples) + return schemas.SeismicIntersectionData( + values_arr_str=json.dumps(vals.values), # pylint: disable=no-member + z_arr_str=json.dumps(z_arr.tolist()), # pylint: disable=no-member + ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 4a306f0d3..01dd9676d 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -1,3 +1,5 @@ +from typing import List + from pydantic import BaseModel @@ -11,3 +13,8 @@ class SeismicCubeMeta(BaseModel): class VdsHandle(BaseModel): sas_token: str vds_url: str + + +class SeismicIntersectionData(BaseModel): + values_arr_str: str + z_arr_str: str diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index d8d3b3477..4e244307f 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -7,6 +7,8 @@ import requests from requests_toolbelt.multipart.decoder import MultipartDecoder +import httpx + from .types import VdsFenceResponse, VdsHandle, VdsMetaData, VdsSliceResponse VDS_HOST_ADDRESS = os.getenv("WEBVIZ_VDS_HOST_ADDRESS") @@ -22,21 +24,24 @@ def __init__(self, sumo_seismic_vds_handle: VdsHandle) -> None: self.sas: str = sumo_seismic_vds_handle.sas_token self.vds_url: str = sumo_seismic_vds_handle.vds_url - def _query(self, endpoint: str, params: dict) -> requests.Response: - """Query the VDS service""" - params.update({"vds": self.vds_url, "sas": self.sas}) - response = requests.post( - f"{VDS_HOST_ADDRESS}/{endpoint}", - headers={"Content-Type": "application/json"}, - data=json.dumps(params), # pylint: disable=maybe-no-member - timeout=60, - ) - - if not response.ok: - raise RuntimeError(f"({str(response.status_code)})-{response.reason}-{response.text} ") + async def _query(self, endpoint: str, params: dict) -> httpx.Response: + """Query the service""" + params.update({"url": self.vds_url, "sas": self.sas}) + + async with httpx.AsyncClient() as client: + response = await client.post( + f"{VDS_HOST_ADDRESS}/{endpoint}", + headers={"Content-Type": "application/json"}, + content=json.dumps(params), + timeout=60, + ) + + if response.is_error: + raise RuntimeError(f"({str(response.status_code)})-{response.reason_phrase}-{response.text}") + return response - def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: + async def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: """Gets a slice in i,j,k direction from the VDS service""" endpoint = "slice" @@ -46,17 +51,20 @@ def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: "vds": self.vds_url, "sas": self.sas, } - response = self._query(endpoint, params) + response = await self._query(endpoint, params) + + # Use MultipartDecoder with httpx's Response content and headers + decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) + parts = decoder.parts - parts = MultipartDecoder.from_response(response).parts metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member shape = (metadata["y"]["samples"], metadata["x"]["samples"]) byte_array = parts[1].content values_np = bytes_to_ndarray(byte_array, list(shape), metadata["format"]) - values = values_np.tolist() + values = values_np.T.tolist() return VdsSliceResponse(values=values, **metadata) - def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cdp") -> VdsFenceResponse: + async def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cdp") -> VdsFenceResponse: """Gets traces along an arbitrary path of x,y coordinates.""" endpoint = "fence" @@ -66,18 +74,22 @@ def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cd "vds": self.vds_url, "sas": self.sas, "interpolation": "linear", - "fillValue": -999, # Assumption is that this will fill out-of-bounds values, but it doesn't seem to work + "fillValue": np.nan, } - response = self._query(endpoint, params) + response = await self._query(endpoint, params) + + # Use MultipartDecoder with httpx's Response content and headers + decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) + parts = decoder.parts - parts = MultipartDecoder.from_response(response).parts metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member byte_array = parts[1].content values_np = bytes_to_ndarray(byte_array, list(metadata["shape"]), metadata["format"]) values = values_np.tolist() + print(metadata) return VdsFenceResponse(values=values, shape=metadata["shape"]) - def get_metadata(self) -> VdsMetaData: + async def get_metadata(self) -> VdsMetaData: """Gets metadata from the cube""" endpoint = "metadata" @@ -85,9 +97,7 @@ def get_metadata(self) -> VdsMetaData: "vds": self.vds_url, "sas": self.sas, } - response = self._query(endpoint, params) - if not response.ok: - raise RuntimeError(response.text) + response = await self._query(endpoint, params) metadata = response.json() return VdsMetaData(**metadata) From 8a2df182637925457ad7b8a2ec35eaf6f77d9e70 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:26:34 +0200 Subject: [PATCH 06/35] docs --- backend/src/backend/primary/routers/seismic/router.py | 1 + backend/src/services/vds_access/vds_access.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index d6c52a137..09368dd34 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -45,6 +45,7 @@ async def get_fence( observed: bool = Query(description="Observed or simulated"), # cutting_plane: schemas.CuttingPlane = Body(alias="cuttingPlane", embed=True), ) -> schemas.SeismicIntersectionData: + """Get a fence of seismic data from a set of coordinates.""" seismic_access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) try: diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 4e244307f..c546941ee 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -18,7 +18,11 @@ class VdsAccess: """Access to the service hosting vds-slice. - https://github.com/equinor/vds-slice""" + https://github.com/equinor/vds-slice + + This access class is used to query the service for slices and fences of seismic data stored in Sumo in vds format. + Note that we are not providing the service with the actual vds file, but rather a SAS token and an URL to the vds file. + """ def __init__(self, sumo_seismic_vds_handle: VdsHandle) -> None: self.sas: str = sumo_seismic_vds_handle.sas_token From d15fa83f4f19e3ecc192493a85d34a11af280132 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:30:31 +0200 Subject: [PATCH 07/35] lint --- backend/src/services/vds_access/vds_access.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index c546941ee..5d42fdc58 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -1,12 +1,10 @@ import logging import os from typing import List +import json import numpy as np -import orjson as json -import requests from requests_toolbelt.multipart.decoder import MultipartDecoder - import httpx from .types import VdsFenceResponse, VdsHandle, VdsMetaData, VdsSliceResponse @@ -61,7 +59,7 @@ async def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) parts = decoder.parts - metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member + metadata = json.loads(parts[0].content) shape = (metadata["y"]["samples"], metadata["x"]["samples"]) byte_array = parts[1].content values_np = bytes_to_ndarray(byte_array, list(shape), metadata["format"]) @@ -86,7 +84,7 @@ async def get_fence(self, coordinates: List[List[float]], coordinate_system: str decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) parts = decoder.parts - metadata = json.loads(parts[0].content) # pylint: disable=maybe-no-member + metadata = json.loads(parts[0].content) byte_array = parts[1].content values_np = bytes_to_ndarray(byte_array, list(metadata["shape"]), metadata["format"]) values = values_np.tolist() From b02bdf1b900d38f6ebedd5e2a76edb915009efdc Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:34:44 +0200 Subject: [PATCH 08/35] lint --- backend/src/backend/primary/routers/seismic/router.py | 6 +++--- backend/src/backend/primary/routers/seismic/schemas.py | 2 -- backend/src/services/vds_access/types.py | 5 ----- backend/src/services/vds_access/vds_access.py | 3 ++- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 09368dd34..d9c4d1e2e 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -3,7 +3,7 @@ import orjson as json import numpy as np -from fastapi import APIRouter, Depends, HTTPException, Query, Body +from fastapi import APIRouter, Depends, HTTPException, Query # , Body from src.services.sumo_access.seismic_access import SeismicAccess from src.services.vds_access.vds_access import VdsAccess @@ -87,6 +87,6 @@ async def get_fence( z_arr = np.linspace(z_axis_meta.min, z_axis_meta.max, z_axis_meta.samples) return schemas.SeismicIntersectionData( - values_arr_str=json.dumps(vals.values), # pylint: disable=no-member - z_arr_str=json.dumps(z_arr.tolist()), # pylint: disable=no-member + values_arr_str=json.dumps(vals.values).decode(), # pylint: disable=no-member + z_arr_str=json.dumps(z_arr.tolist()).decode(), # pylint: disable=no-member ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 01dd9676d..75572f0ae 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -1,5 +1,3 @@ -from typing import List - from pydantic import BaseModel diff --git a/backend/src/services/vds_access/types.py b/backend/src/services/vds_access/types.py index 1127a4183..15c209277 100644 --- a/backend/src/services/vds_access/types.py +++ b/backend/src/services/vds_access/types.py @@ -3,11 +3,6 @@ from pydantic import BaseModel -class VdsHandle(BaseModel): - sas_token: str - vds_url: str - - class VdsAxis(BaseModel): annotation: str max: float diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 5d42fdc58..04571de89 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -7,7 +7,8 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder import httpx -from .types import VdsFenceResponse, VdsHandle, VdsMetaData, VdsSliceResponse +from ..sumo_access.seismic_types import VdsHandle +from .types import VdsFenceResponse, VdsMetaData, VdsSliceResponse VDS_HOST_ADDRESS = os.getenv("WEBVIZ_VDS_HOST_ADDRESS") From db9b4e8f06428b5ef706b668c78c145ffc17b6a0 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:37:14 +0200 Subject: [PATCH 09/35] Add requests-toolbelt --- backend/poetry.lock | 17 ++++++++++++++++- backend/pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index acfb4e39f..e986b2948 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -2327,6 +2327,21 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "rich" version = "13.3.5" @@ -2952,4 +2967,4 @@ tests = ["hypothesis", "pytest", "pytest-benchmark", "pytest-mock", "pytest-snap [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d8ce1c2e79c0682b42ec2165996aa16eeceedf730e5d7c4285af3c34bdd84a57" +content-hash = "026ecb7fc8b06145436955d8ac7f34c8e354fb8c41eecd06a78942cb8c2c123c" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index c59b9dc0f..9d2069538 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -25,6 +25,7 @@ psutil = "^5.9.5" vtk = "^9.2.6" fmu-sumo = "^0.3.10" sumo-wrapper-python = "^0.3.4" +requests-toolbelt = "^1.0.0" [tool.poetry.group.dev.dependencies] From 9b5ae81e4784cee9d460ab93cbfae901906e5c85 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:37:22 +0200 Subject: [PATCH 10/35] Update api --- frontend/src/api/index.ts | 1 + .../src/api/models/SeismicIntersectionData.ts | 9 +++++ frontend/src/api/services/SeismicService.ts | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 frontend/src/api/models/SeismicIntersectionData.ts diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 565094c9f..2a1d5a25a 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -31,6 +31,7 @@ export type { InplaceVolumetricsTableMetaData as 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 type { SeismicIntersectionData as SeismicIntersectionData_api } from './models/SeismicIntersectionData'; 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'; diff --git a/frontend/src/api/models/SeismicIntersectionData.ts b/frontend/src/api/models/SeismicIntersectionData.ts new file mode 100644 index 000000000..bbd4414b9 --- /dev/null +++ b/frontend/src/api/models/SeismicIntersectionData.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type SeismicIntersectionData = { + values_arr_str: string; + z_arr_str: string; +}; + diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index 83c720e58..f4eaf67d0 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ import type { SeismicCubeMeta } from '../models/SeismicCubeMeta'; +import type { SeismicIntersectionData } from '../models/SeismicIntersectionData'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; @@ -35,4 +36,41 @@ export class SeismicService { }); } + /** + * Get Fence + * Get a fence of seismic data from a set of coordinates. + * @param caseUuid Sumo case uuid + * @param ensembleName Ensemble name + * @param realizationNum Realization number + * @param seismicAttribute Seismic cube attribute + * @param timeOrIntervalStr Timestamp or timestep + * @param observed Observed or simulated + * @returns SeismicIntersectionData Successful Response + * @throws ApiError + */ + public getFence( + caseUuid: string, + ensembleName: string, + realizationNum: number, + seismicAttribute: string, + timeOrIntervalStr: string, + observed: boolean, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/seismic/fence/', + query: { + 'case_uuid': caseUuid, + 'ensemble_name': ensembleName, + 'realization_num': realizationNum, + 'seismic_attribute': seismicAttribute, + 'time_or_interval_str': timeOrIntervalStr, + 'observed': observed, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + } From be86a696b2086346fa45850fe737ca3c568e4fb7 Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:00:32 +0200 Subject: [PATCH 11/35] use base64 --- .../primary/routers/seismic/converters.py | 14 +++++ .../backend/primary/routers/seismic/router.py | 9 +-- .../primary/routers/seismic/schemas.py | 7 ++- backend/src/services/vds_access/types.py | 13 ----- backend/src/services/vds_access/vds_access.py | 58 ++++++------------- 5 files changed, 39 insertions(+), 62 deletions(-) create mode 100644 backend/src/backend/primary/routers/seismic/converters.py diff --git a/backend/src/backend/primary/routers/seismic/converters.py b/backend/src/backend/primary/routers/seismic/converters.py new file mode 100644 index 000000000..f20b18f01 --- /dev/null +++ b/backend/src/backend/primary/routers/seismic/converters.py @@ -0,0 +1,14 @@ +from numpy.typing import NDArray + +from src.services.utils.b64 import b64_encode_float_array_as_float32 +from src.services.vds_access.types import VdsAxis + +from .schemas import SeismicIntersectionData + + +def to_api_seismic_fence_data(vds_fence_data: NDArray, z_axis: VdsAxis) -> SeismicIntersectionData: + """ + Convert VDS fence data to API fence data + """ + values_b64arr = b64_encode_float_array_as_float32(vds_fence_data) + return SeismicIntersectionData(values_base64arr=values_b64arr, z_axis=z_axis) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index d9c4d1e2e..634c433b1 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -3,6 +3,7 @@ import orjson as json import numpy as np + from fastapi import APIRouter, Depends, HTTPException, Query # , Body from src.services.sumo_access.seismic_access import SeismicAccess @@ -11,6 +12,7 @@ from src.backend.auth.auth_helper import AuthHelper from . import schemas +from .converters import to_api_seismic_fence_data LOGGER = logging.getLogger(__name__) @@ -84,9 +86,4 @@ async def get_fence( ) meta = await vdsaccess.get_metadata() z_axis_meta = meta.axis[2] - - z_arr = np.linspace(z_axis_meta.min, z_axis_meta.max, z_axis_meta.samples) - return schemas.SeismicIntersectionData( - values_arr_str=json.dumps(vals.values).decode(), # pylint: disable=no-member - z_arr_str=json.dumps(z_arr.tolist()).decode(), # pylint: disable=no-member - ) + return to_api_seismic_fence_data(vals, z_axis_meta) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 75572f0ae..a30a59486 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -1,5 +1,8 @@ from pydantic import BaseModel +from src.services.vds_access.types import VdsAxis +from src.services.utils.b64 import B64FloatArray + class SeismicCubeMeta(BaseModel): seismic_attribute: str @@ -14,5 +17,5 @@ class VdsHandle(BaseModel): class SeismicIntersectionData(BaseModel): - values_arr_str: str - z_arr_str: str + values_base64arr: B64FloatArray + z_axis: VdsAxis diff --git a/backend/src/services/vds_access/types.py b/backend/src/services/vds_access/types.py index 15c209277..3afc0d161 100644 --- a/backend/src/services/vds_access/types.py +++ b/backend/src/services/vds_access/types.py @@ -21,16 +21,3 @@ class VdsMetaData(BaseModel): axis: List[VdsAxis] boundingBox: VdsBoundingBox crs: str - - -class VdsSliceResponse(BaseModel): - values: List[List[float]] - geospatial: List[List[float]] - shape: List[int] - x: VdsAxis - y: VdsAxis - - -class VdsFenceResponse(BaseModel): - values: List[List[float]] - shape: List[int] diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 04571de89..ab461e454 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -4,11 +4,12 @@ import json import numpy as np +from numpy.typing import NDArray from requests_toolbelt.multipart.decoder import MultipartDecoder import httpx from ..sumo_access.seismic_types import VdsHandle -from .types import VdsFenceResponse, VdsMetaData, VdsSliceResponse +from .types import VdsMetaData VDS_HOST_ADDRESS = os.getenv("WEBVIZ_VDS_HOST_ADDRESS") @@ -44,7 +45,7 @@ async def _query(self, endpoint: str, params: dict) -> httpx.Response: return response - async def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: + async def get_slice(self, direction: str, lineno: int) -> NDArray[np.float32]: """Gets a slice in i,j,k direction from the VDS service""" endpoint = "slice" @@ -62,12 +63,13 @@ async def get_slice(self, direction: str, lineno: int) -> VdsSliceResponse: metadata = json.loads(parts[0].content) shape = (metadata["y"]["samples"], metadata["x"]["samples"]) + if metadata["format"] != " VdsFenceResponse: + async def get_fence(self, coordinates: List[List[float]], coordinate_system: str = "cdp") -> NDArray[np.float32]: """Gets traces along an arbitrary path of x,y coordinates.""" endpoint = "fence" @@ -77,8 +79,9 @@ async def get_fence(self, coordinates: List[List[float]], coordinate_system: str "vds": self.vds_url, "sas": self.sas, "interpolation": "linear", - "fillValue": np.nan, + "fillValue": -999, } + response = await self._query(endpoint, params) # Use MultipartDecoder with httpx's Response content and headers @@ -87,10 +90,10 @@ async def get_fence(self, coordinates: List[List[float]], coordinate_system: str metadata = json.loads(parts[0].content) byte_array = parts[1].content - values_np = bytes_to_ndarray(byte_array, list(metadata["shape"]), metadata["format"]) - values = values_np.tolist() - print(metadata) - return VdsFenceResponse(values=values, shape=metadata["shape"]) + if metadata["format"] != " VdsMetaData: """Gets metadata from the cube""" @@ -104,34 +107,7 @@ async def get_metadata(self) -> VdsMetaData: metadata = response.json() return VdsMetaData(**metadata) - # def get_surface_values( - # self, - # xtgeo_surf: RegularSurface, - # above: float, - # below: float, - # attribute: str, - # fill_value: float = -999.25, - # ) -> np.ndarray: - # surface_values = xtgeo_surf.values.filled(fill_value) - # array_shape = surface_values.shape - # endpoint = "attributes/surface/along" - - # timer = PerfTimer() - - # params = { - # "surface": surface_values.tolist(), - # "above": above, - # "below": below, - # "attributes": [attribute], - # } - # response = self._query(endpoint, params) - # print(f"Got attribute surface from VDS in: {timer.elapsed_ms()}ms ({attribute})") - # parts = MultipartDecoder.from_response(response).parts - # seismic_values = np.ndarray(array_shape, "f4", parts[1].content) - # seismic_values = np.ma.masked_equal(seismic_values, fill_value) - # return seismic_values - - -def bytes_to_ndarray(bytes_data: bytes, shape: List[int], dtype: str) -> np.ndarray: + +def bytes_to_ndarray_float22(bytes_data: bytes, shape: List[int]) -> NDArray[np.float32]: """Convert bytes to numpy ndarray""" - return np.ndarray(shape, dtype, bytes_data) + return np.ndarray(shape, " Date: Tue, 3 Oct 2023 09:09:58 +0200 Subject: [PATCH 12/35] update api. lint --- .../src/backend/primary/routers/seismic/router.py | 3 --- frontend/src/api/index.ts | 1 + frontend/src/api/models/SeismicIntersectionData.ts | 7 +++++-- frontend/src/api/models/VdsAxis.ts | 12 ++++++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 frontend/src/api/models/VdsAxis.ts diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 634c433b1..c0febeb29 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -1,8 +1,5 @@ import logging from typing import List -import orjson as json - -import numpy as np from fastapi import APIRouter, Depends, HTTPException, Query # , Body diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 2a1d5a25a..cc7ee413b 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -43,6 +43,7 @@ export type { SurfacePolygonDirectory as SurfacePolygonDirectory_api } from './m export { SurfaceStatisticFunction as SurfaceStatisticFunction_api } from './models/SurfaceStatisticFunction'; export type { UserInfo as UserInfo_api } from './models/UserInfo'; export type { ValidationError as ValidationError_api } from './models/ValidationError'; +export type { VdsAxis as VdsAxis_api } from './models/VdsAxis'; export type { VectorDescription as VectorDescription_api } from './models/VectorDescription'; export type { VectorHistoricalData as VectorHistoricalData_api } from './models/VectorHistoricalData'; export type { VectorRealizationData as VectorRealizationData_api } from './models/VectorRealizationData'; diff --git a/frontend/src/api/models/SeismicIntersectionData.ts b/frontend/src/api/models/SeismicIntersectionData.ts index bbd4414b9..b8fd69c27 100644 --- a/frontend/src/api/models/SeismicIntersectionData.ts +++ b/frontend/src/api/models/SeismicIntersectionData.ts @@ -2,8 +2,11 @@ /* tslint:disable */ /* eslint-disable */ +import type { B64FloatArray } from './B64FloatArray'; +import type { VdsAxis } from './VdsAxis'; + export type SeismicIntersectionData = { - values_arr_str: string; - z_arr_str: string; + values_base64arr: B64FloatArray; + z_axis: VdsAxis; }; diff --git a/frontend/src/api/models/VdsAxis.ts b/frontend/src/api/models/VdsAxis.ts new file mode 100644 index 000000000..2aa516be3 --- /dev/null +++ b/frontend/src/api/models/VdsAxis.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type VdsAxis = { + annotation: string; + max: number; + min: number; + samples: number; + unit: string; +}; + From a4a98e0fca883ae1e34dcddf398173ff581a40c0 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Wed, 11 Oct 2023 13:18:34 +0200 Subject: [PATCH 13/35] Adjust poetry.lock file --- backend/poetry.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index be017c146..ae06689f0 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1666,7 +1666,10 @@ files = [ bottleneck = {version = ">=1.3.2", optional = true, markers = "extra == \"performance\""} numba = {version = ">=0.53.1", optional = true, markers = "extra == \"performance\""} numexpr = {version = ">=2.7.1", optional = true, markers = "extra == \"performance\""} -numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] python-dateutil = ">=2.8.2" pytz = ">=2020.1" tzdata = ">=2022.1" @@ -2327,7 +2330,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3062,4 +3064,4 @@ tests = ["hypothesis", "pytest", "pytest-benchmark", "pytest-mock", "pytest-snap [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9e2918b5054ba8591d7dace93c523e26be1fa807ec8d384d64d4d0e73b655d32" +content-hash = "68feb12fa65d693ae11062acc11db6c8465ed015f62603bb12b34e1403369f19" From ecf2f5ababc6cb6d924763c138ce7e0fe692338d Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 17 Oct 2023 11:39:47 +0200 Subject: [PATCH 14/35] Backup --- .../primary/routers/seismic/converters.py | 14 - .../backend/primary/routers/seismic/router.py | 58 +- .../primary/routers/seismic/schemas.py | 29 +- .../src/services/vds_access/request_types.py | 107 ++++ .../src/services/vds_access/response_types.py | 82 +++ backend/src/services/vds_access/types.py | 23 - backend/src/services/vds_access/vds_access.py | 142 +++-- frontend/package-lock.json | 532 +++++++++++++++++- frontend/package.json | 1 + frontend/src/api/index.ts | 4 +- frontend/src/api/models/Body_get_fence.ts | 10 + ...ntersectionData.ts => SeismicFenceData.ts} | 2 +- .../src/api/models/SeismicFencePolyline.ts | 9 + frontend/src/api/services/SeismicService.ts | 13 +- 14 files changed, 886 insertions(+), 140 deletions(-) delete mode 100644 backend/src/backend/primary/routers/seismic/converters.py create mode 100644 backend/src/services/vds_access/request_types.py create mode 100644 backend/src/services/vds_access/response_types.py delete mode 100644 backend/src/services/vds_access/types.py create mode 100644 frontend/src/api/models/Body_get_fence.ts rename frontend/src/api/models/{SeismicIntersectionData.ts => SeismicFenceData.ts} (85%) create mode 100644 frontend/src/api/models/SeismicFencePolyline.ts diff --git a/backend/src/backend/primary/routers/seismic/converters.py b/backend/src/backend/primary/routers/seismic/converters.py deleted file mode 100644 index f20b18f01..000000000 --- a/backend/src/backend/primary/routers/seismic/converters.py +++ /dev/null @@ -1,14 +0,0 @@ -from numpy.typing import NDArray - -from src.services.utils.b64 import b64_encode_float_array_as_float32 -from src.services.vds_access.types import VdsAxis - -from .schemas import SeismicIntersectionData - - -def to_api_seismic_fence_data(vds_fence_data: NDArray, z_axis: VdsAxis) -> SeismicIntersectionData: - """ - Convert VDS fence data to API fence data - """ - values_b64arr = b64_encode_float_array_as_float32(vds_fence_data) - return SeismicIntersectionData(values_base64arr=values_b64arr, z_axis=z_axis) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 63cee2e0b..060bc97da 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -1,15 +1,20 @@ import logging +import numpy as np +from numpy.typing import NDArray from typing import List -from fastapi import APIRouter, Depends, HTTPException, Query # , Body +from fastapi import APIRouter, Depends, HTTPException, Query, Body from src.services.sumo_access.seismic_access import SeismicAccess from src.services.vds_access.vds_access import VdsAccess from src.services.utils.authenticated_user import AuthenticatedUser from src.backend.auth.auth_helper import AuthHelper +from src.services.utils.b64 import b64_encode_float_array_as_float32 +from services.vds_access.response_types import VdsMetadata +from src.services.vds_access.request_types import VdsCoordinateSystem, VdsCoordinates from . import schemas -from .converters import to_api_seismic_fence_data + LOGGER = logging.getLogger(__name__) @@ -42,9 +47,12 @@ async def get_fence( seismic_attribute: str = Query(description="Seismic cube attribute"), time_or_interval_str: str = Query(description="Timestamp or timestep"), observed: bool = Query(description="Observed or simulated"), - # cutting_plane: schemas.CuttingPlane = Body(alias="cuttingPlane", embed=True), -) -> schemas.SeismicIntersectionData: - """Get a fence of seismic data from a set of coordinates.""" + polyline: schemas.SeismicFencePolyline = Body(alias="seismicFencePolyline", embed=True), +) -> schemas.SeismicFenceData: + """Get a fence of seismic data from a set of (x, y) coordinates.""" + + # NOTE: This is a post request as cutting plane must be a body parameter. Should the naming be changed from "get_fence" to "post_fence"? + seismic_access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) try: @@ -57,30 +65,20 @@ async def get_fence( except ValueError as err: raise HTTPException(status_code=404, detail=str(err)) from err - vdsaccess = VdsAccess(vds_handle) - - vals = await vdsaccess.get_fence( - coordinate_system="cdp", - coordinates=[ - [x, y] - for x, y in zip( - [ - 463156.911, - 463564.402, - 463637.925, - 463690.658, - 463910.452, - ], - [ - 5929542.294, - 5931057.803, - 5931184.235, - 5931278.837, - 5931688.122, - ], - ) - ], + vds_access = VdsAccess(vds_handle) + + # Retrieve fence and post as seismic intersection + values_float32 = await vds_access.get_fence( + coordinate_system=VdsCoordinateSystem.CDP, + coordinates=VdsCoordinates(polyline.x_points, polyline.y_points), ) - meta = await vdsaccess.get_metadata() + + meta: VdsMetadata = await vds_access.get_metadata() + + # Ensure axis len = 3? z_axis_meta = meta.axis[2] - return to_api_seismic_fence_data(vals, z_axis_meta) + + # Provide/return the "shape" of the np.ndarray to the frontend + return schemas.SeismicFenceData( + values_base64arr=b64_encode_float_array_as_float32(values_float32), z_axis=z_axis_meta + ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index a30a59486..8901b5ae1 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -1,9 +1,17 @@ +from typing import List + from pydantic import BaseModel -from src.services.vds_access.types import VdsAxis +from services.vds_access.response_types import VdsAxis from src.services.utils.b64 import B64FloatArray +class SeismicFencePolyline(BaseModel): + x_points: List[float] + y_points: List[float] + # points_xy: List[float] + + class SeismicCubeMeta(BaseModel): seismic_attribute: str iso_date_or_interval: str @@ -11,11 +19,20 @@ class SeismicCubeMeta(BaseModel): is_depth: bool -class VdsHandle(BaseModel): - sas_token: str - vds_url: str +class SeismicFenceData(BaseModel): + """ + Definition of a fence of seismic data from a set of (x, y) coordinates. + + The values array is base64 encoded float array. The fence length defines the number of samples + in length direction of fence. + See: + - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 + """ -class SeismicIntersectionData(BaseModel): values_base64arr: B64FloatArray - z_axis: VdsAxis + num_length_samples: int + num_height_samples: int + min_height: float + max_height: float + # z_axis: VdsAxis diff --git a/backend/src/services/vds_access/request_types.py b/backend/src/services/vds_access/request_types.py new file mode 100644 index 000000000..6dddbf63f --- /dev/null +++ b/backend/src/services/vds_access/request_types.py @@ -0,0 +1,107 @@ +from dataclasses import dataclass +from enum import StrEnum +from typing import List + +""" +This file contains the request types for the vds-slice service found in the following file: + +https://github.com/equinor/vds-slice/blob/master/api/request.go + +Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 + +https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go +""" + + +class VdsInterpolation(StrEnum): + """ + Interpolation options for vds fence + + Source: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L98 + """ + + NEAREST = "nearest" + LINEAR = "linear" + CUBIC = "cubic" + ANGULAR = "angular" + TRIANGULAR = "triangular" + + +class VdsCoordinateSystem(StrEnum): + """ + Coordinate system options for vds fence + + Source: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L86C3-L86C3 + """ + + CDP = "cdp" + IJ = "ij" + ILXL = "ilxl" + + +@dataclass +class VdsCoordinates: + """ + A list coordinates in the VDS coordinate system, as (x, y) points. + + Convert coordinates to format for query request parameter - [[x1,y1], [x2,y2], ..., [xn,yn]] + + Source: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L90 + """ + + x_points: List[float] + y_points: List[float] + + def list(self) -> List[float]: + return [[x, y] for x, y in zip(self.x_points, self.y_points)] + + +@dataclass +class VdsRequestedResource: + """ + Definition of requested vds resource for vds-slice + This is a base class for request types for vds-slice requests + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L13-L35 + """ + + vds: str # blob url + sas: str # sas-token + + def request_parameters(self) -> dict: + raise NotImplementedError + + +@dataclass +class VdsMetadataRequest(VdsRequestedResource): + """ + Definition of metadata request for vds-slice + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L62-L64 + """ + + pass + + +@dataclass +class VdsFenceRequest(VdsRequestedResource): + """ + Definition of a fence request struct for vds-slice + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L76-L105 + """ + + coordinate_system: VdsCoordinateSystem + coordinates: VdsCoordinates + interpolation: VdsInterpolation + fill_value: float + + def request_parameters(self) -> dict: + return { + "vds": self.vds, + "sas": self.sas, + "coordinateSystem": self.coordinate_system.value, + "coordinates": self.coordinates.list(), + "interpolation": self.interpolation.value, + "fillValue": self.fill_value, + } diff --git a/backend/src/services/vds_access/response_types.py b/backend/src/services/vds_access/response_types.py new file mode 100644 index 000000000..b0ee95d57 --- /dev/null +++ b/backend/src/services/vds_access/response_types.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass +from typing import List + +from pydantic import BaseModel + +""" +This file contains the response types for the vds-slice service found in the following file: + +https://github.com/equinor/vds-slice/blob/master/internal/core/core.go + +Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 + +https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go +""" + + +@dataclass +class VdsArray: + """ + Definition of a response array from vds-slice + + A data format is represented by numpy-style formatcodes. + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L96-L103 + """ + + format: str + shape: List[int] + + +@dataclass +class VdsFenceMetadata(VdsArray): + """ + Definition of a fence metadata response from vds-slice + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L160-L162 + """ + + pass + + +class VdsAxis(BaseModel): + """ + Definition of an axis from vds-slice + + Neglected: + - stepsize: float + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 + """ + + annotation: str + max: float + min: float + samples: int + unit: str + + +class VdsBoundingBox(BaseModel): + """ + Definition of a bounding box from vds-slice + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L90-L94 + """ + + cdp: List[List[float]] + ij: List[List[float]] + ilxl: List[List[float]] + + +class VdsMetadata(BaseModel): + """ + Definition of metadata from vds-slice + + + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L140-L157 + """ + + axis: List[VdsAxis] + boundingBox: VdsBoundingBox + crs: str diff --git a/backend/src/services/vds_access/types.py b/backend/src/services/vds_access/types.py deleted file mode 100644 index 3afc0d161..000000000 --- a/backend/src/services/vds_access/types.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import List - -from pydantic import BaseModel - - -class VdsAxis(BaseModel): - annotation: str - max: float - min: float - samples: int - unit: str - - -class VdsBoundingBox(BaseModel): - cdp: List[List[float]] - ij: List[List[float]] - ilxl: List[List[float]] - - -class VdsMetaData(BaseModel): - axis: List[VdsAxis] - boundingBox: VdsBoundingBox - crs: str diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index ab461e454..40672efae 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -3,19 +3,33 @@ from typing import List import json + import numpy as np from numpy.typing import NDArray -from requests_toolbelt.multipart.decoder import MultipartDecoder +from requests_toolbelt.multipart.decoder import MultipartDecoder, BodyPart import httpx from ..sumo_access.seismic_types import VdsHandle -from .types import VdsMetaData +from .response_types import VdsMetadata, VdsFenceMetadata +from .request_types import ( + VdsCoordinates, + VdsCoordinateSystem, + VdsInterpolation, + VdsFenceRequest, + VdsRequestedResource, + VdsMetadataRequest, +) VDS_HOST_ADDRESS = os.getenv("WEBVIZ_VDS_HOST_ADDRESS") LOGGER = logging.getLogger(__name__) +def bytes_to_ndarray_float32(bytes_data: bytes, shape: List[int]) -> NDArray[np.float32]: + """Convert bytes to numpy ndarray""" + return np.ndarray(shape, " None: self.sas: str = sumo_seismic_vds_handle.sas_token self.vds_url: str = sumo_seismic_vds_handle.vds_url - async def _query(self, endpoint: str, params: dict) -> httpx.Response: + self._interpolation = VdsInterpolation.LINEAR + + # async def _query(self, endpoint: str, request: VdsRequestedResource) -> httpx.Response: + async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response: """Query the service""" - params.update({"url": self.vds_url, "sas": self.sas}) + + # request.sas = self.sas + # request.vds = self.vds_url async with httpx.AsyncClient() as client: response = await client.post( f"{VDS_HOST_ADDRESS}/{endpoint}", headers={"Content-Type": "application/json"}, - content=json.dumps(params), + content=json.dumps(request.request_parameters()), timeout=60, ) @@ -45,69 +64,82 @@ async def _query(self, endpoint: str, params: dict) -> httpx.Response: return response - async def get_slice(self, direction: str, lineno: int) -> NDArray[np.float32]: - """Gets a slice in i,j,k direction from the VDS service""" - - endpoint = "slice" - params = { - "direction": direction, - "lineno": lineno, - "vds": self.vds_url, - "sas": self.sas, - } - response = await self._query(endpoint, params) - - # Use MultipartDecoder with httpx's Response content and headers - decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) - parts = decoder.parts + # async def get_slice(self, direction: str, lineno: int) -> NDArray[np.float32]: + # """Gets a slice in i,j,k direction from the VDS service""" + + # endpoint = "slice" + # params = { + # "direction": direction, + # "lineno": lineno, + # "vds": self.vds_url, + # "sas": self.sas, + # } + # response = await self._query(endpoint, params) + + # # Use MultipartDecoder with httpx's Response content and headers + # decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) + # parts = decoder.parts + + # metadata = json.loads(parts[0].content) + # shape = (metadata["y"]["samples"], metadata["x"]["samples"]) + # if metadata["format"] != " NDArray[np.float32]: + ) -> np.ndarray: + """ + Gets traces along an arbitrary path of (x, y) coordinates + + Returns: + """ - metadata = json.loads(parts[0].content) - shape = (metadata["y"]["samples"], metadata["x"]["samples"]) - if metadata["format"] != " NDArray[np.float32]: - """Gets traces along an arbitrary path of x,y coordinates.""" endpoint = "fence" + hard_coded_fill_value = -999 - params = { - "coordinateSystem": coordinate_system, - "coordinates": coordinates, - "vds": self.vds_url, - "sas": self.sas, - "interpolation": "linear", - "fillValue": -999, - } + fence_request = VdsFenceRequest( + vds=self.vds_url, + sas=self.sas, + coordinate_system=coordinate_system, + coordinates=coordinates, + interpolation=self._interpolation, + fill_value=hard_coded_fill_value, + ) - response = await self._query(endpoint, params) + response = await self._query(endpoint, fence_request) # Use MultipartDecoder with httpx's Response content and headers decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) parts = decoder.parts - metadata = json.loads(parts[0].content) + # Validate parts from decoded response - metadata, data + if len(parts) != 2 or not parts[0].content or not parts[1].content: + raise ValueError(f"Expected two parts, got {len(parts)}") + + # Expect each part in parts tuple to be BodyPart + if not isinstance(parts[0], BodyPart) or not isinstance(parts[1], BodyPart): + raise ValueError(f"Expected parts to be BodyPart, got {type(parts[0])}, {type(parts[1])}") + + metadata = VdsFenceMetadata(**parts[0].content) byte_array = parts[1].content - if metadata["format"] != " VdsMetaData: + async def get_metadata(self) -> VdsMetadata: """Gets metadata from the cube""" endpoint = "metadata" - params = { - "vds": self.vds_url, - "sas": self.sas, - } - response = await self._query(endpoint, params) - metadata = response.json() - return VdsMetaData(**metadata) - + metadata_request = VdsMetadataRequest(vds=self.vds_url, sas=self.sas) + response = await self._query(endpoint, metadata_request) -def bytes_to_ndarray_float22(bytes_data: bytes, shape: List[int]) -> NDArray[np.float32]: - """Convert bytes to numpy ndarray""" - return np.ndarray(shape, "=4.2" } }, + "node_modules/@equinor/esv-intersection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@equinor/esv-intersection/-/esv-intersection-3.0.10.tgz", + "integrity": "sha512-YFS2E9SOEXr3tOE1bhGcIcVooWy/ZiRTodxA3eXafuPHNT+bBUVPNmFnyVA8vuSFIDi+J+gr9F2yz6RiCsLngQ==", + "dependencies": { + "@equinor/videx-math": "^1.1.0", + "@equinor/videx-vector2": "^1.0.44", + "curve-interpolator": "3.1.1", + "d3-array": "^3.2.4", + "d3-axis": "^3.0.0", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "pixi.js": "^7.1.0" + } + }, + "node_modules/@equinor/videx-linear-algebra": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@equinor/videx-linear-algebra/-/videx-linear-algebra-1.0.7.tgz", + "integrity": "sha512-DvS5f/gl3mvrWk8a9vTBjlygLufA7j9SRyU5TsjaGzTcLX0r/VY1QxbpwVXLk1L6G96HkZgJ5nNiTWA/zZx+Ig==", + "dependencies": { + "@equinor/videx-math": "^1.0.12" + } + }, + "node_modules/@equinor/videx-math": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@equinor/videx-math/-/videx-math-1.1.0.tgz", + "integrity": "sha512-nOooZbUpVunXHBlfkUHs9Srh7zomtTlfJPsfp9u/9P0Yp/dBnbg4va2X0LHuN1tVBllYQc7P9boelyoK3dSxCA==" + }, + "node_modules/@equinor/videx-vector2": { + "version": "1.0.44", + "resolved": "https://registry.npmjs.org/@equinor/videx-vector2/-/videx-vector2-1.0.44.tgz", + "integrity": "sha512-di7xb6McIjLX8t3UcIRHAt81V+KLuDQ1pq9PD7V3IgrK3P/phLxgZg3VwcUHvrXNnEoI7Y7lWUjd1KdosSNRmQ==", + "dependencies": { + "@equinor/videx-linear-algebra": "^1.0.7" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.18.17", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", @@ -3111,6 +3152,389 @@ "node": ">= 8" } }, + "node_modules/@pixi/accessibility": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.3.1.tgz", + "integrity": "sha512-7+XqUbVIRKvZQOuzQkt5vGpaDIBMorK5Sa+y9exu7nYDPCYlPdVoQeiQL7v2PtUxqkrgn28faNn8OqY8MhcqrQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/events": "7.3.1" + } + }, + "node_modules/@pixi/app": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.3.1.tgz", + "integrity": "sha512-pQG/IIgsQLXmB/Y+qeS6g/VBF824XiCKtypgYVtdg+hwKdVN4aVhayBtvFBmxz+abRBXUdHSUhBG80haPXEtCQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/assets": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.3.1.tgz", + "integrity": "sha512-BACPZkm5ptkqXpdHvV0tdAzsVR9t9iQROa5K4OOGkyHUZhLFcoSPFu3Vfa77rzgo3xtTK+HTw3qABibr3RBcVw==", + "peer": true, + "dependencies": { + "@types/css-font-loading-module": "^0.0.7" + }, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/utils": "7.3.1" + } + }, + "node_modules/@pixi/color": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.3.1.tgz", + "integrity": "sha512-qxDy9iEUbhR+n5zDBUUeWx9aReaELpjjP0BVneHNQxLELpx3sVxCHBXjnKe5ZTGtZsJOkD5ji7gDBfwFlUurBw==", + "peer": true, + "dependencies": { + "@pixi/colord": "^2.9.4" + } + }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", + "peer": true + }, + "node_modules/@pixi/compressed-textures": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.3.1.tgz", + "integrity": "sha512-1zH6vDR1+xnYaaDpno8zGkTNT/itP9J8d+dfFcKPUmzeqpm8batPkY5O7NtJ8SrQSN0UwJb+X7gFwK8LwVgKWQ==", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.3.1", + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/constants": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.3.1.tgz", + "integrity": "sha512-Xq8tyUcVwpkPge9XGMZl9gzzNdNuKmjmxulJ9jNk9Zbf2QPVunn21WWZYBl4TO0mleUhT8wNnbXXn5CAbMgwFA==", + "peer": true + }, + "node_modules/@pixi/core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.3.1.tgz", + "integrity": "sha512-LEASg6x2wp46y2WmN227K8X3vQKU0KxflrYGgj/o573OYSpEJkmY0WMoEInTUXmhCVLOHPcnBde5YDu0JmKsjQ==", + "peer": true, + "dependencies": { + "@pixi/color": "7.3.1", + "@pixi/constants": "7.3.1", + "@pixi/extensions": "7.3.1", + "@pixi/math": "7.3.1", + "@pixi/runner": "7.3.1", + "@pixi/settings": "7.3.1", + "@pixi/ticker": "7.3.1", + "@pixi/utils": "7.3.1", + "@types/offscreencanvas": "^2019.6.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/@pixi/display": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.3.1.tgz", + "integrity": "sha512-FzVsEuR9mmi8G+3HytrnKpcO9By04gJhNrYnMY6pLWwXE6MzuEtC7QYobtN1UIUp4Zs73O6lmdZtWOYqrdjgGw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/events": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.3.1.tgz", + "integrity": "sha512-L6MSeFnLo3z/pQMCLGbwp5szp1+26YoZ50De2pT6hMuPGtMtio89IkFU07G1jNuiGhJsPMw4Zn9H71KiZp+X1A==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/extensions": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.3.1.tgz", + "integrity": "sha512-KiM1uXnNQ6yjJ/8gYuqBVZxw1CPH0LRoUvnOWXrHXm74somNtJdxnEliq3zagGpyxjmiHHJmI0Y9gnXrhzVU4Q==", + "peer": true + }, + "node_modules/@pixi/extract": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.3.1.tgz", + "integrity": "sha512-kuB2gi5JHdBK0WOvvbKZiVQkQFqRZehJlDbZaiaWcgb9G9B/8M/V+fodXS/WsmTNxOegN98igkSTpD2k1dYPnA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-alpha": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.3.1.tgz", + "integrity": "sha512-6vTUFnqLuPGFgG5/ODuNcmZNmsykWo/sMR4jBekeINGoVHs3Min0nQE4t/zk2xtg7UA1reI8SQzcVhBqp8g7GQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-blur": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.3.1.tgz", + "integrity": "sha512-aRK7CfKh/tnCbXS3um9LKjiQ1TOsFTPfI+OOOxcaQyZK9qs4GinnhZ2HCoq5OAcMnjMAWhvi60ssKJsP8505zw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-color-matrix": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.3.1.tgz", + "integrity": "sha512-XcVyWbk91QbTpzyIV/+nQlJxsgU3QO/rOTlota18aHB4EF8ptYyVeRgIv1YXi+UxcWRE/MpWbeUVLaJG9buELA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-displacement": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.3.1.tgz", + "integrity": "sha512-XrjBJcWvNxy/LlNZU6vLghUzG9kqMAM6xAIOzFm7CPwr+3Av32fyBp2l6FUYgoZMgb+4+Rxv8mPnUyX4gFMqLw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-fxaa": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.3.1.tgz", + "integrity": "sha512-ZL3kjIkMm29+G8AOuxDDo2VrqeJEBw6zloDDerUq71NRsyIUWJEdscdiVUDytL1/JqZwxdHEvzDNG6IF3P1VFg==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/filter-noise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.3.1.tgz", + "integrity": "sha512-eORqa7Rn3OxZlrrF4J59VPHHgiHe+cukPQ2Wp24Q0XuYoBZrnPow5SDDwtwIfCXQg68LPdiSpKdwkLJxsew8aw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/graphics": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.3.1.tgz", + "integrity": "sha512-e1GfhN0fCtq1VDPSgd7nm034ml79SqmP0+IqX3CoilfT7Qoy+IimEYZ4R50SczGBcUGE4egDRIFYv+TgdBuQ2g==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/math": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.3.1.tgz", + "integrity": "sha512-kRTpac/3frRPMf8YRai3+2bFldgE7SwzGsLYjb4//QdeuY0WVaeSPJJKEVXZ/aBK/JgGO7Np75kwr7/2+qegSg==", + "peer": true + }, + "node_modules/@pixi/mesh": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.3.1.tgz", + "integrity": "sha512-22b2++/UWURee2R++1PaY/zTh4UaN/ASg4DHcELI9OgM5qrxfnk3MrpdWTNyJPYWUTLlP8XYPGiwIbVPiiUNmA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/mesh-extras": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.3.1.tgz", + "integrity": "sha512-xmK6c/cM5iDFqrg7NEjSH9i4Ieqsy9A9MWAFoIoPTph6HhCTZWNnRn6sdgLR9+6z5b8AmXujwarMBqdM7fXtvQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/mesh": "7.3.1" + } + }, + "node_modules/@pixi/mixin-cache-as-bitmap": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.3.1.tgz", + "integrity": "sha512-4GNr8qq0ZFqSVnIsfqlNI5cnCz1qL9kLhq+/SRf3JmPcwYolnS015HP5zhnG2nKU6pgeCz5KpQfxOYPytOlcKw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/mixin-get-child-by-name": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.3.1.tgz", + "integrity": "sha512-+2tArxvfjmbWW+/8sfn+qCtNy2kUCa1xPhyGjfgLwPBAOAghpmIXWa5YXXVVdUgE2+qw3aL5OyR2n/DL1Y5/6A==", + "peer": true, + "peerDependencies": { + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/mixin-get-global-position": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.3.1.tgz", + "integrity": "sha512-WcPxxj73u/c2AXaCd8umi9xipMllOg3Cl3LJ08Dx/EA0Ce1TNg6aTr+Apu696IZ5BiXcyyelSMaPnxu6cSdCcg==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/particle-container": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.3.1.tgz", + "integrity": "sha512-NNJph+0eZwqdrPTaxOTv7KhVjvBRi5Edrduv8orDKYcF47gOvlCzzQbKR1hvXDMkzWou18eQwO/OH/NIgQBgFA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/prepare": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.3.1.tgz", + "integrity": "sha512-8dCgtUWcOotD+Y649luzOUiReoSwc2WbT1DQcq9w47BvnbDrBzZQ5KxsIUqHXW70IcT+hShqHxt6KZavCHqdMQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/graphics": "7.3.1", + "@pixi/text": "7.3.1" + } + }, + "node_modules/@pixi/runner": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.3.1.tgz", + "integrity": "sha512-N0mQCSwX9jmG63ALy85b0fLXXFu8XS9jUnzhz7T6cA3gGtAtVHBhc5Vh5ZJfFB/FhCQmeTg48PEL6t2Ea3Ogjg==", + "peer": true + }, + "node_modules/@pixi/settings": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.3.1.tgz", + "integrity": "sha512-bC2MdSW7Jg1vT4tUy0zvlottuylGhsHfuy22ZEvQHiwk9dy0y8gC7H98my8bMjnUQNWFpfamhmVGxoMM/K95FQ==", + "peer": true, + "dependencies": { + "@pixi/constants": "7.3.1", + "@types/css-font-loading-module": "^0.0.7", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/@pixi/sprite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.3.1.tgz", + "integrity": "sha512-hSyrzcrNpGiDwc5BVnCfCMkA2Ikx+RBIXyi/Q9K6L0I2gZR8iHUWiQUJKC4jnhDVvvVcQADqiAyIBslXYQLLDw==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1" + } + }, + "node_modules/@pixi/sprite-animated": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.3.1.tgz", + "integrity": "sha512-adAs9ZRss6ioaGqmSYvjR2b+rfaC+hPwqNGyA1TwqKmgxS5c5pSPMWgiNYRRKPS7qss5T0KOWjfcHxdBHM62mg==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/sprite-tiling": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.3.1.tgz", + "integrity": "sha512-/A739b3TNncoAOqrJcy8wKa2O/aa17luAvMUrYOO3ZLsj0lqIj3lMCjNqlBsfftctAwQlmc6dM8radyeyzIsNQ==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/spritesheet": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.3.1.tgz", + "integrity": "sha512-wsCTmUnQSUp+BMrDcGERzvqdoRImNqUNXV2mj+HZyfN2ZN/kyqCxwdWhq2TrbZiAOW6PsASn8516Cei7ECJp7w==", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.3.1", + "@pixi/core": "7.3.1" + } + }, + "node_modules/@pixi/text": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.3.1.tgz", + "integrity": "sha512-73KB83+fZSww2LXXofJHLgOuNwk/8h0hir5shL8vdae8bgupv4VAw9wOG15WeSZdXoa7fU1owWyDM3RWLTmv+Q==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/sprite": "7.3.1" + } + }, + "node_modules/@pixi/text-bitmap": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.3.1.tgz", + "integrity": "sha512-sc/eoV/OOJh81/GKqIXfuUexQ0EbP2ggPvGQghaC/VyUbOAwlSMUmNmXlY68KzPX9JasIDCFyv07Utnkmona7g==", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.3.1", + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/mesh": "7.3.1", + "@pixi/text": "7.3.1" + } + }, + "node_modules/@pixi/text-html": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.3.1.tgz", + "integrity": "sha512-Yo96c/fM7eSUdovZInTl/LkI44y2s2vfdkpnPjHU6ohIFJ4cAf4ok2CjBpAMExvJG8f4lvTkDRqECuRUD0DUnA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/sprite": "7.3.1", + "@pixi/text": "7.3.1" + } + }, + "node_modules/@pixi/ticker": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.3.1.tgz", + "integrity": "sha512-S4ur5MQgVM0yTeJVALbRA3WkX/df/6DG6XXVoR9JaFOQ3zgXhh05O+0NFFebj4b3Ls3/y5aG6aOkkoBl1AXrCw==", + "peer": true, + "dependencies": { + "@pixi/extensions": "7.3.1", + "@pixi/settings": "7.3.1", + "@pixi/utils": "7.3.1" + } + }, + "node_modules/@pixi/utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-VSWebpMwOfoVuzjd0XlntmQK0RXP1UuRx1jyRuq0m/9VNnnNsZMXpmi47Adb7N1RFmlQ2FFxDxLzxmCzm9ZfTA==", + "peer": true, + "dependencies": { + "@pixi/color": "7.3.1", + "@pixi/constants": "7.3.1", + "@pixi/settings": "7.3.1", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4012,12 +4436,24 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "peer": true + }, "node_modules/@types/culori": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/culori/-/culori-2.0.0.tgz", "integrity": "sha512-bKpEra39sQS9UZ+1JoWhuGJEzwKS0dUkNCohVYmn6CAEBkqyIXimKiPDRZWtiOB7sKgkWMaTUpHFimygRoGIlg==", "dev": true }, + "node_modules/@types/earcut": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.2.tgz", + "integrity": "sha512-EU6fwVNP1TGVTkCILfURtzzwJq/ie5LgipELnzCINgm4VdDIkkbB8wnLSe81J77Bbqf4MiO3sJGhWzc6MCp5dQ==", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", @@ -5274,7 +5710,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -5892,6 +6327,11 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/curve-interpolator": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/curve-interpolator/-/curve-interpolator-3.1.1.tgz", + "integrity": "sha512-Pn0sjZbJ/bZT0hxtLtU7AsbNGsKROGQEF5VShIQetvCM84MsmAso8CSsOWECbY/js4EnV7IURbKGiUwELyYv0w==" + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -7614,6 +8054,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "peer": true + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8133,7 +8579,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -8696,7 +9141,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8708,7 +9152,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9354,6 +9797,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "peer": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -11993,7 +12442,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12428,6 +12876,48 @@ "node": ">= 6" } }, + "node_modules/pixi.js": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.3.1.tgz", + "integrity": "sha512-BFuEOsNimxjLq/Bo16JiXBr/EKXp13z/NXdyMasRcZumCHDUdrKvTEFaUfrkFat6ZghgrWyHnREzK3b+F6OX0Q==", + "peer": true, + "dependencies": { + "@pixi/accessibility": "7.3.1", + "@pixi/app": "7.3.1", + "@pixi/assets": "7.3.1", + "@pixi/compressed-textures": "7.3.1", + "@pixi/core": "7.3.1", + "@pixi/display": "7.3.1", + "@pixi/events": "7.3.1", + "@pixi/extensions": "7.3.1", + "@pixi/extract": "7.3.1", + "@pixi/filter-alpha": "7.3.1", + "@pixi/filter-blur": "7.3.1", + "@pixi/filter-color-matrix": "7.3.1", + "@pixi/filter-displacement": "7.3.1", + "@pixi/filter-fxaa": "7.3.1", + "@pixi/filter-noise": "7.3.1", + "@pixi/graphics": "7.3.1", + "@pixi/mesh": "7.3.1", + "@pixi/mesh-extras": "7.3.1", + "@pixi/mixin-cache-as-bitmap": "7.3.1", + "@pixi/mixin-get-child-by-name": "7.3.1", + "@pixi/mixin-get-global-position": "7.3.1", + "@pixi/particle-container": "7.3.1", + "@pixi/prepare": "7.3.1", + "@pixi/sprite": "7.3.1", + "@pixi/sprite-animated": "7.3.1", + "@pixi/sprite-tiling": "7.3.1", + "@pixi/spritesheet": "7.3.1", + "@pixi/text": "7.3.1", + "@pixi/text-bitmap": "7.3.1", + "@pixi/text-html": "7.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -12929,6 +13419,21 @@ } ] }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "peer": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quadbin": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/quadbin/-/quadbin-0.1.9.tgz", @@ -13715,7 +14220,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -15001,6 +15505,22 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "peer": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "peer": true + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 50d30eef6..48c1f309c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "test": "jest" }, "dependencies": { + "@equinor/esv-intersection": "^3.0.10", "@headlessui/react": "^1.7.8", "@mui/base": "^5.0.0-beta.3", "@mui/icons-material": "^5.14.9", diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index cc7ee413b..9854eca73 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -11,6 +11,7 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export { B64FloatArray as B64FloatArray_api } from './models/B64FloatArray'; export { B64UintArray as B64UintArray_api } from './models/B64UintArray'; +export type { Body_get_fence as Body_get_fence_api } from './models/Body_get_fence'; export type { Body_get_realizations_response as Body_get_realizations_response_api } from './models/Body_get_realizations_response'; export type { CaseInfo as CaseInfo_api } from './models/CaseInfo'; export type { Completions as Completions_api } from './models/Completions'; @@ -31,7 +32,8 @@ export type { InplaceVolumetricsTableMetaData as 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 type { SeismicIntersectionData as SeismicIntersectionData_api } from './models/SeismicIntersectionData'; +export type { SeismicFenceData as SeismicFenceData_api } from './models/SeismicFenceData'; +export type { SeismicFencePolyline as SeismicFencePolyline_api } from './models/SeismicFencePolyline'; 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'; diff --git a/frontend/src/api/models/Body_get_fence.ts b/frontend/src/api/models/Body_get_fence.ts new file mode 100644 index 000000000..88669a12b --- /dev/null +++ b/frontend/src/api/models/Body_get_fence.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { SeismicFencePolyline } from './SeismicFencePolyline'; + +export type Body_get_fence = { + polyline: SeismicFencePolyline; +}; + diff --git a/frontend/src/api/models/SeismicIntersectionData.ts b/frontend/src/api/models/SeismicFenceData.ts similarity index 85% rename from frontend/src/api/models/SeismicIntersectionData.ts rename to frontend/src/api/models/SeismicFenceData.ts index b8fd69c27..4cd837009 100644 --- a/frontend/src/api/models/SeismicIntersectionData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -5,7 +5,7 @@ import type { B64FloatArray } from './B64FloatArray'; import type { VdsAxis } from './VdsAxis'; -export type SeismicIntersectionData = { +export type SeismicFenceData = { values_base64arr: B64FloatArray; z_axis: VdsAxis; }; diff --git a/frontend/src/api/models/SeismicFencePolyline.ts b/frontend/src/api/models/SeismicFencePolyline.ts new file mode 100644 index 000000000..664b5794f --- /dev/null +++ b/frontend/src/api/models/SeismicFencePolyline.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type SeismicFencePolyline = { + x_points: Array; + y_points: Array; +}; + diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index f4eaf67d0..2dedb338e 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -1,8 +1,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { Body_get_fence } from '../models/Body_get_fence'; import type { SeismicCubeMeta } from '../models/SeismicCubeMeta'; -import type { SeismicIntersectionData } from '../models/SeismicIntersectionData'; +import type { SeismicFenceData } from '../models/SeismicFenceData'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; @@ -38,14 +39,15 @@ export class SeismicService { /** * Get Fence - * Get a fence of seismic data from a set of coordinates. + * Get a fence of seismic data from a set of (x, y) coordinates. * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param realizationNum Realization number * @param seismicAttribute Seismic cube attribute * @param timeOrIntervalStr Timestamp or timestep * @param observed Observed or simulated - * @returns SeismicIntersectionData Successful Response + * @param requestBody + * @returns SeismicFenceData Successful Response * @throws ApiError */ public getFence( @@ -55,7 +57,8 @@ export class SeismicService { seismicAttribute: string, timeOrIntervalStr: string, observed: boolean, - ): CancelablePromise { + requestBody: Body_get_fence, + ): CancelablePromise { return this.httpRequest.request({ method: 'POST', url: '/seismic/fence/', @@ -67,6 +70,8 @@ export class SeismicService { 'time_or_interval_str': timeOrIntervalStr, 'observed': observed, }, + body: requestBody, + mediaType: 'application/json', errors: { 422: `Validation Error`, }, From 5de3a3260add94a088d0e99b81b339c086cdeabb Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Wed, 18 Oct 2023 14:17:06 +0200 Subject: [PATCH 15/35] Backup and with jupyter notebook testing of API --- backend/.testing/__init__.py | 0 backend/seismic_vds_slice.ipynb | 104 ++++++++++++++++++ .../backend/primary/routers/seismic/router.py | 26 +++-- .../primary/routers/seismic/schemas.py | 35 ++++-- .../primary/routers/timeseries/router.py | 1 + .../src/services/vds_access/request_types.py | 13 ++- .../src/services/vds_access/response_types.py | 5 +- backend/src/services/vds_access/vds_access.py | 54 +++++---- 8 files changed, 196 insertions(+), 42 deletions(-) create mode 100644 backend/.testing/__init__.py create mode 100644 backend/seismic_vds_slice.ipynb diff --git a/backend/.testing/__init__.py b/backend/.testing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/seismic_vds_slice.ipynb b/backend/seismic_vds_slice.ipynb new file mode 100644 index 000000000..9b89eeb1d --- /dev/null +++ b/backend/seismic_vds_slice.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RESOURCE_SCOPES_DICT={'sumo': ['api://88d2b022-3539-4dda-9e66-853801334a86/access_as_user'], 'smda': ['api://691a29c5-8199-4e87-80a2-16bd71e831cd/user_impersonation']}\n" + ] + }, + { + "ename": "TypeError", + "evalue": "VdsAccess.__init__() missing 1 required positional argument: 'vds_url'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/home/jorgen/Git/webviz/backend/seismic_vds_slice.ipynb Cell 1\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 33\u001b[0m seismic_access \u001b[39m=\u001b[39m SeismicAccess(sumo_client\u001b[39m=\u001b[39msumo_client, case\u001b[39m=\u001b[39mcase, case_uuid\u001b[39m=\u001b[39mcase_uuid, iteration_name\u001b[39m=\u001b[39miteration)\n\u001b[1;32m 35\u001b[0m vds_handle \u001b[39m=\u001b[39m \u001b[39mawait\u001b[39;00m seismic_access\u001b[39m.\u001b[39mget_vds_handle(seismic_attribute\u001b[39m=\u001b[39mcube_tagname,realization\u001b[39m=\u001b[39mrealization, time_or_interval_str\u001b[39m=\u001b[39mtimestamp)\n\u001b[0;32m---> 36\u001b[0m vds_access \u001b[39m=\u001b[39m VdsAccess(vds_handle)\n\u001b[1;32m 40\u001b[0m x_points \u001b[39m=\u001b[39m [\u001b[39m463156.911\u001b[39m, \u001b[39m463564.402\u001b[39m, \u001b[39m463637.925\u001b[39m, \u001b[39m463690.658\u001b[39m, \u001b[39m463910.452\u001b[39m]\n\u001b[1;32m 41\u001b[0m y_points \u001b[39m=\u001b[39m [\u001b[39m5929542.294\u001b[39m, \u001b[39m5931057.803\u001b[39m, \u001b[39m5931184.235\u001b[39m, \u001b[39m5931278.837\u001b[39m, \u001b[39m5931688.122\u001b[39m]\n", + "\u001b[0;31mTypeError\u001b[0m: VdsAccess.__init__() missing 1 required positional argument: 'vds_url'" + ] + } + ], + "source": [ + "# Set env variable before import of vds_access!\n", + "import os\n", + "os.environ[\"WEBVIZ_VDS_HOST_ADDRESS\"] = \"https://server-oneseismictest-dev.playground.radix.equinor.com\"\n", + "\n", + "from sumo.wrapper import SumoClient\n", + "\n", + "# from fmu.sumo.explorer import Explorer\n", + "# from fmu.sumo.explorer.objects.cube_collection import CubeCollection\n", + "from fmu.sumo.explorer.objects import CaseCollection\n", + "\n", + "from services.vds_access.vds_access import VdsAccess\n", + "from services.sumo_access.seismic_access import SeismicAccess\n", + "from services.vds_access.request_types import VdsCoordinates\n", + "\n", + "import numpy as np\n", + "\n", + "\n", + "\n", + "# Test data\n", + "case_uuid = \"c619f32d-3ada-4e5e-8d3c-330f940e88f8\"\n", + "realization = 0\n", + "iteration = \"iter-0\"\n", + "cube_tagname = \"amplitude_far_time\"\n", + "timestep = None\n", + "timestamp = \"2018-01-01T00:00:00\"\n", + "observed = False\n", + "\n", + "sumo_client = SumoClient(env=\"prod\")\n", + "case_collection = CaseCollection(sumo_client).filter(uuid=case_uuid) # Existing working case\n", + "case = case_collection[0]\n", + "\n", + "# Setup\n", + "seismic_access = SeismicAccess(sumo_client=sumo_client, case=case, case_uuid=case_uuid, iteration_name=iteration)\n", + "\n", + "vds_handle = await seismic_access.get_vds_handle(seismic_attribute=cube_tagname,realization=realization, time_or_interval_str=timestamp)\n", + "vds_access = VdsAccess(sas_token=vds_handle.sas_token, vds_url=vds_handle.vds_url)\n", + "\n", + "\n", + "\n", + "x_points = [463156.911, 463564.402, 463637.925, 463690.658, 463910.452]\n", + "y_points = [5929542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122]\n", + "\n", + "x_points = [463156.911, 463564.402, 463637.925, 463690.658]\n", + "y_points = [5929542.294, 5931057.803, 5931184.235, 5931278.837,]\n", + "\n", + "result = await vds_access.get_fence(coordinates=VdsCoordinates(x_points=x_points, y_points=y_points))\n", + "\n", + "\n", + "print(result.shape)\n", + "display(result)\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "backend-ODIrNPVE-py3.11", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 060bc97da..b60bb881b 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Body -from src.services.sumo_access.seismic_access import SeismicAccess +from src.services.sumo_access.seismic_access import SeismicAccess, VdsHandle from src.services.vds_access.vds_access import VdsAccess from src.services.utils.authenticated_user import AuthenticatedUser from src.backend.auth.auth_helper import AuthHelper @@ -49,14 +49,13 @@ async def get_fence( observed: bool = Query(description="Observed or simulated"), polyline: schemas.SeismicFencePolyline = Body(alias="seismicFencePolyline", embed=True), ) -> schemas.SeismicFenceData: - """Get a fence of seismic data from a set of (x, y) coordinates.""" - + """Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system.""" # NOTE: This is a post request as cutting plane must be a body parameter. Should the naming be changed from "get_fence" to "post_fence"? seismic_access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) try: - vds_handle = seismic_access.get_vds_handle( + vds_handle: VdsHandle = seismic_access.get_vds_handle( realization=realization_num, seismic_attribute=seismic_attribute, time_or_interval_str=time_or_interval_str, @@ -65,20 +64,25 @@ async def get_fence( except ValueError as err: raise HTTPException(status_code=404, detail=str(err)) from err - vds_access = VdsAccess(vds_handle) + vds_access = VdsAccess(sas_token=vds_handle.sas_token, vds_url=vds_handle.vds_url) # Retrieve fence and post as seismic intersection - values_float32 = await vds_access.get_fence( + fence_traces_ndarray_float32 = await vds_access.get_fence_traces_as_ndarray( coordinate_system=VdsCoordinateSystem.CDP, coordinates=VdsCoordinates(polyline.x_points, polyline.y_points), ) + if len(fence_traces_ndarray_float32.shape) != 2: + raise ValueError(f"Expected fence traces array of 2 dimensions, got {len(fence_traces_ndarray_float32.shape)}") meta: VdsMetadata = await vds_access.get_metadata() + if len(meta.axis) != 3: + raise ValueError(f"Expected 3 axes, got {len(meta.axis)}") + depth_axis_meta = meta.axis[2] - # Ensure axis len = 3? - z_axis_meta = meta.axis[2] - - # Provide/return the "shape" of the np.ndarray to the frontend return schemas.SeismicFenceData( - values_base64arr=b64_encode_float_array_as_float32(values_float32), z_axis=z_axis_meta + fence_traces_encoded=b64_encode_float_array_as_float32(fence_traces_ndarray_float32), + num_traces=fence_traces_ndarray_float32.shape[0], + num_trace_samples=fence_traces_ndarray_float32.shape[1], + min_height=depth_axis_meta.min, # TODO: Should this be depth_axis_meta.max? + max_height=depth_axis_meta.max, # TODO: Should this be depth_axis_meta.min? ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 8901b5ae1..c800b47ff 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -7,6 +7,17 @@ class SeismicFencePolyline(BaseModel): + """ + x- and y-coordinates of a polyline defining a fence of seismic data. + + Note: Coordinates are in domain coordinate system (UTM). + + NOTE: + - Verify coordinates are in domain coordinate system (UTM)? + - Consider points_xy array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? + - Ensure equal length of x_points and y_points arrays? + """ + x_points: List[float] y_points: List[float] # points_xy: List[float] @@ -21,18 +32,28 @@ class SeismicCubeMeta(BaseModel): class SeismicFenceData(BaseModel): """ - Definition of a fence of seismic data from a set of (x, y) coordinates. + Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. + + The values array is base64 encoded float array. The number of traces is the number of traces along length direction + of the fence, and the number of trace samples is the number of samples along the height/depth axis of the fence. + + The fence is defined as follows: - The values array is base64 encoded float array. The fence length defines the number of samples - in length direction of fence. + trace1 trace2 trace3 + |-------|-------| + |-------|-------| + Height |-------|-------| + |-------|-------| + |-------|-------| See: - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 """ - values_base64arr: B64FloatArray - num_length_samples: int - num_height_samples: int + fence_traces_encoded: B64FloatArray # Encoded flattened array of fence values + # num_length_samples:int + # num_height_samples: int + num_traces: int + num_trace_samples: int min_height: float max_height: float - # z_axis: VdsAxis diff --git a/backend/src/backend/primary/routers/timeseries/router.py b/backend/src/backend/primary/routers/timeseries/router.py index 65847c7cf..f6e15108e 100644 --- a/backend/src/backend/primary/routers/timeseries/router.py +++ b/backend/src/backend/primary/routers/timeseries/router.py @@ -28,6 +28,7 @@ async def get_vector_list( """Get list of all vectors in a given Sumo ensemble, excluding any historical vectors""" access = await SummaryAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + print("SUMO ACCESS TOKEN: " + authenticated_user.get_sumo_access_token()) vector_info_arr = await access.get_available_vectors() ret_arr: list[schemas.VectorDescription] = [ diff --git a/backend/src/services/vds_access/request_types.py b/backend/src/services/vds_access/request_types.py index 6dddbf63f..b9cb8c256 100644 --- a/backend/src/services/vds_access/request_types.py +++ b/backend/src/services/vds_access/request_types.py @@ -42,7 +42,7 @@ class VdsCoordinateSystem(StrEnum): @dataclass class VdsCoordinates: """ - A list coordinates in the VDS coordinate system, as (x, y) points. + A list of coordinates in the VDS coordinate system, as (x, y) points. Convert coordinates to format for query request parameter - [[x1,y1], [x2,y2], ..., [xn,yn]] @@ -52,7 +52,14 @@ class VdsCoordinates: x_points: List[float] y_points: List[float] - def list(self) -> List[float]: + def __init__(self, x_points: List[float], y_points: List[float]) -> None: + if len(x_points) != len(y_points): + raise ValueError("x_points and y_points must be of equal length") + + self.x_points = x_points + self.y_points = y_points + + def to_list(self) -> List[float]: return [[x, y] for x, y in zip(self.x_points, self.y_points)] @@ -101,7 +108,7 @@ def request_parameters(self) -> dict: "vds": self.vds, "sas": self.sas, "coordinateSystem": self.coordinate_system.value, - "coordinates": self.coordinates.list(), + "coordinates": self.coordinates.to_list(), "interpolation": self.interpolation.value, "fillValue": self.fill_value, } diff --git a/backend/src/services/vds_access/response_types.py b/backend/src/services/vds_access/response_types.py index b0ee95d57..16a79377d 100644 --- a/backend/src/services/vds_access/response_types.py +++ b/backend/src/services/vds_access/response_types.py @@ -19,7 +19,10 @@ class VdsArray: """ Definition of a response array from vds-slice - A data format is represented by numpy-style formatcodes. + format: A data format is represented by numpy-style formatcodes. + shape: Shape of an array, e.g. [10,50] for a 2D array with 10 rows and 50 columns. + + See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L96-L103 """ diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 40672efae..545375ede 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -26,8 +26,13 @@ def bytes_to_ndarray_float32(bytes_data: bytes, shape: List[int]) -> NDArray[np.float32]: - """Convert bytes to numpy ndarray""" - return np.ndarray(shape, " None: - self.sas: str = sumo_seismic_vds_handle.sas_token - self.vds_url: str = sumo_seismic_vds_handle.vds_url + def __init__(self, sas_token: str, vds_url: str) -> None: + self.sas: str = sas_token + self.vds_url: str = vds_url self._interpolation = VdsInterpolation.LINEAR # async def _query(self, endpoint: str, request: VdsRequestedResource) -> httpx.Response: + @staticmethod async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response: """Query the service""" - # request.sas = self.sas - # request.vds = self.vds_url - async with httpx.AsyncClient() as client: response = await client.post( f"{VDS_HOST_ADDRESS}/{endpoint}", @@ -88,16 +91,23 @@ async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response # values_np = bytes_to_ndarray_float32(byte_array, list(shape)) # return values_np - async def get_fence( - self, - coordinates: VdsCoordinates, - coordinate_system: VdsCoordinateSystem = VdsCoordinateSystem.CDP - # ) -> NDArray[np.float32]: - ) -> np.ndarray: + async def get_fence_traces_as_ndarray( + self, coordinates: VdsCoordinates, coordinate_system: VdsCoordinateSystem = VdsCoordinateSystem.CDP + ) -> NDArray[np.float32]: """ - Gets traces along an arbitrary path of (x, y) coordinates + Gets traces along an arbitrary path of (x, y) coordinates. - Returns: + The traces are perpendicular on the on the coordinates in the x-y plane. The number of traces are + equal to the number of (x, y) coordinates, and each trace has the same number of samples equal the + depth of the seismic cube. + + TODO: Consider return ndarray with shape vs return 1D array with metadata? + + `Returns:` + np.ndarray - with: dtype=float32, shape=[num_traces, num_trace_samples] and order='C' + + * num_traces = number of traces along the length of the fence, i.e. number of (x, y) coordinates + * num_trace_samples = number of samples in each trace, i.e. number of values along the height/depth axis of the fence. """ endpoint = "fence" @@ -112,13 +122,14 @@ async def get_fence( fill_value=hard_coded_fill_value, ) + # Fence query returns two parts - metadata and data response = await self._query(endpoint, fence_request) # Use MultipartDecoder with httpx's Response content and headers decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) parts = decoder.parts - # Validate parts from decoded response - metadata, data + # Validate parts from decoded response if len(parts) != 2 or not parts[0].content or not parts[1].content: raise ValueError(f"Expected two parts, got {len(parts)}") @@ -126,13 +137,16 @@ async def get_fence( if not isinstance(parts[0], BodyPart) or not isinstance(parts[1], BodyPart): raise ValueError(f"Expected parts to be BodyPart, got {type(parts[0])}, {type(parts[1])}") - metadata = VdsFenceMetadata(**parts[0].content) + metadata = VdsFenceMetadata(**json.loads(parts[0].content)) byte_array = parts[1].content if metadata.format != " VdsMetadata: """Gets metadata from the cube""" From 814205abdb266566f6d16d00c504b9e968fe1962 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 19 Oct 2023 11:46:49 +0200 Subject: [PATCH 16/35] Adjustments after merge --- backend/.testing/__init__.py | 0 .../backend/primary/routers/seismic/router.py | 24 +++++++++++---- .../primary/routers/seismic/schemas.py | 26 ++++++++++------- .../primary/routers/timeseries/router.py | 1 - .../src/services/vds_access/request_types.py | 9 +++++- frontend/src/api/index.ts | 1 - frontend/src/api/models/SeismicFenceData.ts | 29 +++++++++++++++++-- .../src/api/models/SeismicFencePolyline.ts | 12 ++++++++ frontend/src/api/models/VdsAxis.ts | 12 -------- frontend/src/api/services/SeismicService.ts | 12 +++++++- 10 files changed, 91 insertions(+), 35 deletions(-) delete mode 100644 backend/.testing/__init__.py delete mode 100644 frontend/src/api/models/VdsAxis.ts diff --git a/backend/.testing/__init__.py b/backend/.testing/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index b60bb881b..3692214dc 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -10,7 +10,7 @@ from src.services.utils.authenticated_user import AuthenticatedUser from src.backend.auth.auth_helper import AuthHelper from src.services.utils.b64 import b64_encode_float_array_as_float32 -from services.vds_access.response_types import VdsMetadata +from src.services.vds_access.response_types import VdsMetadata from src.services.vds_access.request_types import VdsCoordinateSystem, VdsCoordinates from . import schemas @@ -49,10 +49,23 @@ async def get_fence( observed: bool = Query(description="Observed or simulated"), polyline: schemas.SeismicFencePolyline = Body(alias="seismicFencePolyline", embed=True), ) -> schemas.SeismicFenceData: - """Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system.""" + """Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. + + The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. + Each trace has number of samples equal length, and is a set of values along the height/depth axis of the fence. + + The returned data + * fence_traces_encoded: array of traces is a base64 encoded flattened float32 array of trace values. Decoding info: [num_traces, num_trace_samples] + * num_traces: Number of traces in fence + * num_trace_samples: Number of samples in each trace + * min_height: Minimum height/depth value of fence + * max_height: Maximum height/depth value of fence + """ # NOTE: This is a post request as cutting plane must be a body parameter. Should the naming be changed from "get_fence" to "post_fence"? - seismic_access = SeismicAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + seismic_access = await SeismicAccess.from_case_uuid( + authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name + ) try: vds_handle: VdsHandle = seismic_access.get_vds_handle( @@ -66,10 +79,11 @@ async def get_fence( vds_access = VdsAccess(sas_token=vds_handle.sas_token, vds_url=vds_handle.vds_url) - # Retrieve fence and post as seismic intersection + # Retrieve fence and post as seismic intersection using cdp coordinates for vds-slice + # NOTE: Correct coordinate format and scaling - see VdsCoordinateSystem? fence_traces_ndarray_float32 = await vds_access.get_fence_traces_as_ndarray( - coordinate_system=VdsCoordinateSystem.CDP, coordinates=VdsCoordinates(polyline.x_points, polyline.y_points), + coordinate_system=VdsCoordinateSystem.CDP, ) if len(fence_traces_ndarray_float32.shape) != 2: raise ValueError(f"Expected fence traces array of 2 dimensions, got {len(fence_traces_ndarray_float32.shape)}") diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index c800b47ff..ef22f01d9 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -2,13 +2,14 @@ from pydantic import BaseModel -from services.vds_access.response_types import VdsAxis from src.services.utils.b64 import B64FloatArray class SeismicFencePolyline(BaseModel): """ - x- and y-coordinates of a polyline defining a fence of seismic data. + (x, y) points defining a polyline in domain coordinate system, to retrieve fence of seismic data. + + Expect equal number of x- and y-points. Note: Coordinates are in domain coordinate system (UTM). @@ -34,17 +35,20 @@ class SeismicFenceData(BaseModel): """ Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. - The values array is base64 encoded float array. The number of traces is the number of traces along length direction - of the fence, and the number of trace samples is the number of samples along the height/depth axis of the fence. + - fence_traces_encoded: The fence trace array is base64 encoded float array. + - num_traces: The number of traces along length/width direction of the fence, i.e. the number of (x, y) coordinates. + - num_trace_samples: The number of samples in each trace, i.e. the number of values along the height/depth axis of the fence. + - min_height: The minimum height/depth value of the fence. + - max_height: The maximum height/depth value of the fence. - The fence is defined as follows: + Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. - trace1 trace2 trace3 - |-------|-------| - |-------|-------| - Height |-------|-------| - |-------|-------| - |-------|-------| + trace1 trace2 trace3 + |-------|-------| + |-------|-------| + |-------|-------| Height/depth axis + |-------|-------| + |-------|-------| See: - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 diff --git a/backend/src/backend/primary/routers/timeseries/router.py b/backend/src/backend/primary/routers/timeseries/router.py index f6e15108e..65847c7cf 100644 --- a/backend/src/backend/primary/routers/timeseries/router.py +++ b/backend/src/backend/primary/routers/timeseries/router.py @@ -28,7 +28,6 @@ async def get_vector_list( """Get list of all vectors in a given Sumo ensemble, excluding any historical vectors""" access = await SummaryAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - print("SUMO ACCESS TOKEN: " + authenticated_user.get_sumo_access_token()) vector_info_arr = await access.get_available_vectors() ret_arr: list[schemas.VectorDescription] = [ diff --git a/backend/src/services/vds_access/request_types.py b/backend/src/services/vds_access/request_types.py index b9cb8c256..5413e30a9 100644 --- a/backend/src/services/vds_access/request_types.py +++ b/backend/src/services/vds_access/request_types.py @@ -31,6 +31,13 @@ class VdsCoordinateSystem(StrEnum): """ Coordinate system options for vds fence + * ilxl: inline, crossline pairs + * ij: Coordinates are given as in 0-indexed system, where the first + line in each direction is 0 and the last is number-of-lines - 1. + * cdp: Coordinates are given as cdpx/cdpy pairs. In the original SEGY + this would correspond to the cdpx and cdpy fields in the + trace-headers after applying the scaling factor. + Source: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L86C3-L86C3 """ @@ -42,7 +49,7 @@ class VdsCoordinateSystem(StrEnum): @dataclass class VdsCoordinates: """ - A list of coordinates in the VDS coordinate system, as (x, y) points. + A list of coordinates in the selected VdsCoordinateSystem, as (x, y) points. Convert coordinates to format for query request parameter - [[x1,y1], [x2,y2], ..., [xn,yn]] diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index b4378956d..7df9ad7f5 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -46,7 +46,6 @@ export type { SurfacePolygonDirectory as SurfacePolygonDirectory_api } from './m export { SurfaceStatisticFunction as SurfaceStatisticFunction_api } from './models/SurfaceStatisticFunction'; export type { UserInfo as UserInfo_api } from './models/UserInfo'; export type { ValidationError as ValidationError_api } from './models/ValidationError'; -export type { VdsAxis as VdsAxis_api } from './models/VdsAxis'; export type { VectorDescription as VectorDescription_api } from './models/VectorDescription'; export type { VectorHistoricalData as VectorHistoricalData_api } from './models/VectorHistoricalData'; export type { VectorRealizationData as VectorRealizationData_api } from './models/VectorRealizationData'; diff --git a/frontend/src/api/models/SeismicFenceData.ts b/frontend/src/api/models/SeismicFenceData.ts index 4cd837009..32d2b710a 100644 --- a/frontend/src/api/models/SeismicFenceData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -3,10 +3,33 @@ /* eslint-disable */ import type { B64FloatArray } from './B64FloatArray'; -import type { VdsAxis } from './VdsAxis'; +/** + * Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. + * + * - fence_traces_encoded: The fence trace array is base64 encoded float array. + * - num_traces: The number of traces along length/width direction of the fence, i.e. the number of (x, y) coordinates. + * - num_trace_samples: The number of samples in each trace, i.e. the number of values along the height/depth axis of the fence. + * - min_height: The minimum height/depth value of the fence. + * - max_height: The maximum height/depth value of the fence. + * + * Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. + * + * trace1 trace2 trace3 + * |-------|-------| + * |-------|-------| + * |-------|-------| Height/depth axis + * |-------|-------| + * |-------|-------| + * + * See: + * - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 + */ export type SeismicFenceData = { - values_base64arr: B64FloatArray; - z_axis: VdsAxis; + fence_traces_encoded: B64FloatArray; + num_traces: number; + num_trace_samples: number; + min_height: number; + max_height: number; }; diff --git a/frontend/src/api/models/SeismicFencePolyline.ts b/frontend/src/api/models/SeismicFencePolyline.ts index 664b5794f..70027385d 100644 --- a/frontend/src/api/models/SeismicFencePolyline.ts +++ b/frontend/src/api/models/SeismicFencePolyline.ts @@ -2,6 +2,18 @@ /* tslint:disable */ /* eslint-disable */ +/** + * (x, y) points defining a polyline in domain coordinate system, to retrieve fence of seismic data. + * + * Expect equal number of x- and y-points. + * + * Note: Coordinates are in domain coordinate system (UTM). + * + * NOTE: + * - Verify coordinates are in domain coordinate system (UTM)? + * - Consider points_xy array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? + * - Ensure equal length of x_points and y_points arrays? + */ export type SeismicFencePolyline = { x_points: Array; y_points: Array; diff --git a/frontend/src/api/models/VdsAxis.ts b/frontend/src/api/models/VdsAxis.ts deleted file mode 100644 index 2aa516be3..000000000 --- a/frontend/src/api/models/VdsAxis.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type VdsAxis = { - annotation: string; - max: number; - min: number; - samples: number; - unit: string; -}; - diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index 2dedb338e..9a463d2c3 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -39,7 +39,17 @@ export class SeismicService { /** * Get Fence - * Get a fence of seismic data from a set of (x, y) coordinates. + * Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. + * + * The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. + * Each trace has number of samples equal length, and is a set of values along the height/depth axis of the fence. + * + * The returned data + * * fence_traces_encoded: array of traces is a base64 encoded flattened float32 array of trace values. Decoding info: [num_traces, num_trace_samples] + * * num_traces: Number of traces in fence + * * num_trace_samples: Number of samples in each trace + * * min_height: Minimum height/depth value of fence + * * max_height: Maximum height/depth value of fence * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param realizationNum Realization number From 99f7ec0e2fc3826a83ffeeca58667cd2eae64bf8 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Fri, 20 Oct 2023 15:27:22 +0200 Subject: [PATCH 17/35] Backup --- backend/seismic_vds_slice.ipynb | 83 ++++++++++--- .../backend/primary/routers/seismic/router.py | 29 +++-- .../primary/routers/seismic/schemas.py | 48 ++++---- .../src/services/vds_access/request_types.py | 2 +- backend/src/services/vds_access/vds_access.py | 115 +++++++++++------- frontend/src/api/models/SeismicFenceData.ts | 34 +++--- frontend/src/api/services/SeismicService.ts | 10 +- .../SeismicIntersection/loadModule.tsx | 23 ++++ .../SeismicIntersection/queryHooks.tsx | 69 +++++++++++ .../SeismicIntersection/registerModule.ts | 13 ++ .../modules/SeismicIntersection/settings.tsx | 115 ++++++++++++++++++ .../src/modules/SeismicIntersection/state.ts | 8 ++ .../src/modules/SeismicIntersection/types.ts | 13 ++ .../utils/queryDataTransforms.ts | 22 ++++ .../utils/seismicCubeDirectory.ts | 53 ++++++++ .../src/modules/SeismicIntersection/view.tsx | 25 ++++ frontend/src/modules/registerAllModules.ts | 1 + 17 files changed, 547 insertions(+), 116 deletions(-) create mode 100644 frontend/src/modules/SeismicIntersection/loadModule.tsx create mode 100644 frontend/src/modules/SeismicIntersection/queryHooks.tsx create mode 100644 frontend/src/modules/SeismicIntersection/registerModule.ts create mode 100644 frontend/src/modules/SeismicIntersection/settings.tsx create mode 100644 frontend/src/modules/SeismicIntersection/state.ts create mode 100644 frontend/src/modules/SeismicIntersection/types.ts create mode 100644 frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts create mode 100644 frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts create mode 100644 frontend/src/modules/SeismicIntersection/view.tsx diff --git a/backend/seismic_vds_slice.ipynb b/backend/seismic_vds_slice.ipynb index 9b89eeb1d..34dc05de1 100644 --- a/backend/seismic_vds_slice.ipynb +++ b/backend/seismic_vds_slice.ipynb @@ -2,26 +2,49 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "RESOURCE_SCOPES_DICT={'sumo': ['api://88d2b022-3539-4dda-9e66-853801334a86/access_as_user'], 'smda': ['api://691a29c5-8199-4e87-80a2-16bd71e831cd/user_impersonation']}\n" + "axis=[VdsAxis(annotation='Inline', max=217.0, min=0.0, samples=218, unit='unitless'), VdsAxis(annotation='Crossline', max=137.0, min=0.0, samples=138, unit='unitless'), VdsAxis(annotation='Sample', max=1999.0, min=1500.0, samples=500, unit='ms')] boundingBox=VdsBoundingBox(cdp=[[461484.34, 5926563.2], [456083.37, 5936006.48], [462069.5080733945, 5939430.173027524], [467470.4780733945, 5929986.8930275235]], ij=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]], ilxl=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]]) crs=''\n", + "Vds Access coordinate system: \n", + "Sumo case coordinate system: ST_WGS84_UTM37N_P32637\n", + "Flattened fence traces array: [0. 0. 0. ... 0. 0. 0.]\n", + "Number of traces: 4\n", + "Number of samples in trace: 500\n" ] }, { - "ename": "TypeError", - "evalue": "VdsAccess.__init__() missing 1 required positional argument: 'vds_url'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/jorgen/Git/webviz/backend/seismic_vds_slice.ipynb Cell 1\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 33\u001b[0m seismic_access \u001b[39m=\u001b[39m SeismicAccess(sumo_client\u001b[39m=\u001b[39msumo_client, case\u001b[39m=\u001b[39mcase, case_uuid\u001b[39m=\u001b[39mcase_uuid, iteration_name\u001b[39m=\u001b[39miteration)\n\u001b[1;32m 35\u001b[0m vds_handle \u001b[39m=\u001b[39m \u001b[39mawait\u001b[39;00m seismic_access\u001b[39m.\u001b[39mget_vds_handle(seismic_attribute\u001b[39m=\u001b[39mcube_tagname,realization\u001b[39m=\u001b[39mrealization, time_or_interval_str\u001b[39m=\u001b[39mtimestamp)\n\u001b[0;32m---> 36\u001b[0m vds_access \u001b[39m=\u001b[39m VdsAccess(vds_handle)\n\u001b[1;32m 40\u001b[0m x_points \u001b[39m=\u001b[39m [\u001b[39m463156.911\u001b[39m, \u001b[39m463564.402\u001b[39m, \u001b[39m463637.925\u001b[39m, \u001b[39m463690.658\u001b[39m, \u001b[39m463910.452\u001b[39m]\n\u001b[1;32m 41\u001b[0m y_points \u001b[39m=\u001b[39m [\u001b[39m5929542.294\u001b[39m, \u001b[39m5931057.803\u001b[39m, \u001b[39m5931184.235\u001b[39m, \u001b[39m5931278.837\u001b[39m, \u001b[39m5931688.122\u001b[39m]\n", - "\u001b[0;31mTypeError\u001b[0m: VdsAccess.__init__() missing 1 required positional argument: 'vds_url'" - ] + "data": { + "text/plain": [ + "[['t11', 't12', 't13'], ['t21', 't22', 't23'], ['t31', 't32', 't33']]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "array(['t11', 't12', 't13', 't21', 't22', 't23', 't31', 't32', 't33'],\n", + " dtype=' dict: - raise NotImplementedError + return {"vds": self.vds, "sas": self.sas} @dataclass diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 545375ede..305e61e11 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -1,15 +1,13 @@ import logging import os -from typing import List +from typing import List, Tuple import json - import numpy as np from numpy.typing import NDArray from requests_toolbelt.multipart.decoder import MultipartDecoder, BodyPart import httpx -from ..sumo_access.seismic_types import VdsHandle from .response_types import VdsMetadata, VdsFenceMetadata from .request_types import ( VdsCoordinates, @@ -35,6 +33,13 @@ def bytes_to_ndarray_float32(bytes_data: bytes, shape: List[int]) -> NDArray[np. return np.ndarray(shape=shape, dtype=" NDArray[np.float32]: + """ + Convert bytes to numpy flatten ndarray with row-major order, i.e. "C" order + """ + return np.ndarray(shape=shape, dtype=" httpx.Response return response - # async def get_slice(self, direction: str, lineno: int) -> NDArray[np.float32]: - # """Gets a slice in i,j,k direction from the VDS service""" - - # endpoint = "slice" - # params = { - # "direction": direction, - # "lineno": lineno, - # "vds": self.vds_url, - # "sas": self.sas, - # } - # response = await self._query(endpoint, params) - - # # Use MultipartDecoder with httpx's Response content and headers - # decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) - # parts = decoder.parts - - # metadata = json.loads(parts[0].content) - # shape = (metadata["y"]["samples"], metadata["x"]["samples"]) - # if metadata["format"] != " VdsMetadata: + """Gets metadata from the cube""" + endpoint = "metadata" + + metadata_request = VdsMetadataRequest(vds=self.vds_url, sas=self.sas) + response = await self._query(endpoint, metadata_request) + + metadata = response.json() + return VdsMetadata(**metadata) + + async def get_flattened_fence_traces_array_and_metadata( self, coordinates: VdsCoordinates, coordinate_system: VdsCoordinateSystem = VdsCoordinateSystem.CDP - ) -> NDArray[np.float32]: + ) -> Tuple[NDArray[np.float32], int, int]: """ - Gets traces along an arbitrary path of (x, y) coordinates. + Gets traces along an arbitrary path of (x, y) coordinates, with a trace per coordinate. - The traces are perpendicular on the on the coordinates in the x-y plane. The number of traces are - equal to the number of (x, y) coordinates, and each trace has the same number of samples equal the - depth of the seismic cube. + The traces are perpendicular on the on the coordinates in the x-y plane, and each trace has number + of samples equal to the depth of the seismic cube. - TODO: Consider return ndarray with shape vs return 1D array with metadata? + With traces perpendicular to the x-y plane, the traces are defined to go along the depth direction + of the fence. `Returns:` - np.ndarray - with: dtype=float32, shape=[num_traces, num_trace_samples] and order='C' + `Tuple[flattened_fence_traces_array: NDArray[np.float32], num_traces: int, num_trace_samples: int]` + + `flattened_fence_traces_array`: 1D np.ndarray with dtype=float32, stored trace by trace. The array has length `num_traces x num_trace_samples`.\n + `num_traces`: number of traces along the length of the fence, i.e. number of (x, y) coordinates.\n + `num_trace_samples`: number of samples in each trace, i.e. number of values along the height/depth axis of the fence.\n + - * num_traces = number of traces along the length of the fence, i.e. number of (x, y) coordinates - * num_trace_samples = number of samples in each trace, i.e. number of values along the height/depth axis of the fence. + \n`Description:` + + With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. + + `2D Fence Trace Array:` + + ``` + [[t11, t12, ..., t1n], + [t21, t22, ..., t2n], + ... , + [tm1, tm2, ..., tmn]] + ``` + + \n`Flattened 2D trace array with row major order:` + + ``` + [t11, t12, ..., t1n, t21, t22, ..., t2n, ..., tm1, tm2, ..., tmn] + ``` + + \n`Visualization Example:` + + ``` + trace_1 trace_2 trace_m + |--------|--- ... ---| sample_1 + |--------|--- ... ---| sample_2 + . + . + . + |--------|--- ... ---| sample_n-1 + |--------|--- ... ---| sample_n + ``` """ endpoint = "fence" @@ -146,14 +171,12 @@ async def get_fence_traces_as_ndarray( if len(metadata.shape) != 2: raise ValueError(f"Expected shape to be 2D, got {metadata.shape}") - return bytes_to_ndarray_float32(byte_array, shape=metadata.shape) - - async def get_metadata(self) -> VdsMetadata: - """Gets metadata from the cube""" - endpoint = "metadata" + # TODO: Drop and just provide flattened straight away? + fence_traces_ndarray_float32 = bytes_to_ndarray_float32(byte_array, shape=metadata.shape) - metadata_request = VdsMetadataRequest(vds=self.vds_url, sas=self.sas) - response = await self._query(endpoint, metadata_request) + # Flattened array with row major order, i.e. C-order in numpy + flattened_fence_traces_float32_array = bytes_to_flatten_ndarray_float32( + fence_traces_ndarray_float32, shape=fence_traces_ndarray_float32.shape + ) - metadata = response.json() - return VdsMetadata(**metadata) + return (flattened_fence_traces_float32_array, metadata.shape[0], metadata.shape[1]) diff --git a/frontend/src/api/models/SeismicFenceData.ts b/frontend/src/api/models/SeismicFenceData.ts index 32d2b710a..b90f7bf27 100644 --- a/frontend/src/api/models/SeismicFenceData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -6,30 +6,34 @@ import type { B64FloatArray } from './B64FloatArray'; /** * Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. + * Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. * - * - fence_traces_encoded: The fence trace array is base64 encoded float array. - * - num_traces: The number of traces along length/width direction of the fence, i.e. the number of (x, y) coordinates. - * - num_trace_samples: The number of samples in each trace, i.e. the number of values along the height/depth axis of the fence. - * - min_height: The minimum height/depth value of the fence. - * - max_height: The maximum height/depth value of the fence. + * The trace is along the along length direction of the fence. * - * Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. + * `Properties:` + * - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. + * - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline, and implies traces + * - `num_trace_samples`: The number of samples in each trace. + * - `min_fence_depth`: The minimum depth value of the fence. + * - `max_fence_depth`: The maximum depth value of the fence. + * + * `Description - fence_traces_b64arr:` + * + * The encoded fence trace array is a flattened array of traces, where data is stored trace by trace. + * With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. + * + * Fence traces 1D array: [trace_1, trace_2, ..., trace_m] * - * trace1 trace2 trace3 - * |-------|-------| - * |-------|-------| - * |-------|-------| Height/depth axis - * |-------|-------| - * |-------|-------| + * Trace 1D array: [sample_1, sample_2, ..., sample_n] * * See: * - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 */ export type SeismicFenceData = { - fence_traces_encoded: B64FloatArray; + fence_traces_b64arr: B64FloatArray; num_traces: number; num_trace_samples: number; - min_height: number; - max_height: number; + min_fence_depth: number; + max_fence_depth: number; }; diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index 9a463d2c3..122e34d5a 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -45,11 +45,13 @@ export class SeismicService { * Each trace has number of samples equal length, and is a set of values along the height/depth axis of the fence. * * The returned data - * * fence_traces_encoded: array of traces is a base64 encoded flattened float32 array of trace values. Decoding info: [num_traces, num_trace_samples] - * * num_traces: Number of traces in fence + * * fence_traces_b64arr: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. Decoding info: [num_traces, num_trace_samples] + * * num_traces: Number of traces in fence array * * num_trace_samples: Number of samples in each trace - * * min_height: Minimum height/depth value of fence - * * max_height: Maximum height/depth value of fence + * * min_fence_depth: The minimum depth value of the fence. + * * max_fence_depth: The maximum depth value of the fence. + * + * TODO: Replace time_or_interval_str with time_or_interval: schemas.TimeOrInterval? * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param realizationNum Realization number diff --git a/frontend/src/modules/SeismicIntersection/loadModule.tsx b/frontend/src/modules/SeismicIntersection/loadModule.tsx new file mode 100644 index 000000000..d82514593 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/loadModule.tsx @@ -0,0 +1,23 @@ +import { ModuleRegistry } from "@framework/ModuleRegistry"; + +import { settings } from "./settings"; +import { State } from "./state"; +import { view } from "./view"; + +const defaultState: State = { + wellBoreAddress: { uwi: "55/33-A-4", uuid: "drogon_horizontal", type: "smda" }, + seismicAddress: null, + // viewSettings: { + // showGridParameter: false, + // showSeismic: true, + // showSurfaces: true, + // showWellMarkers: true, + // extension: 1000, + // zScale: 5, + // }, +}; + +const module = ModuleRegistry.initModule("SeismicIntersection", defaultState); + +module.viewFC = view; +module.settingsFC = settings; diff --git a/frontend/src/modules/SeismicIntersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx new file mode 100644 index 000000000..ff2c2e8df --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/queryHooks.tsx @@ -0,0 +1,69 @@ +import { Body_get_fence_api, SeismicCubeMeta_api, SeismicFencePolyline_api } from "@api"; +import { apiService } from "@framework/ApiService"; +import { UseQueryResult, useQuery } from "@tanstack/react-query"; + +import { SeismicFenceData_trans, transformSeismicFenceData } from "./utils/queryDataTransforms"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export function useSeismicCubeDirectoryQuery( + caseUuid: string | undefined, + ensembleName: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: ["getSeismicDirectory", caseUuid, ensembleName], + queryFn: () => apiService.seismic.getSeismicDirectory(caseUuid ?? "", ensembleName ?? ""), + staleTime: STALE_TIME, + cacheTime: CACHE_TIME, + enabled: caseUuid && ensembleName ? true : false, + }); +} + +export function useSeismicFenceDataQuery( + caseUuid: string | null, + ensembleName: string | null, + realizationNum: number | null, + seismicAttribute: string | null, + timeOrIntervalStr: string | null, + observed: boolean | null, + polyline: SeismicFencePolyline_api | null, + allowEnable: boolean +): UseQueryResult { + const bodyPolyline: Body_get_fence_api = { polyline: polyline ?? { x_points: [], y_points: [] } }; + return useQuery({ + queryKey: [ + "getFence", + caseUuid, + ensembleName, + realizationNum, + seismicAttribute, + timeOrIntervalStr, + observed, + polyline, + ], + queryFn: () => + apiService.seismic.getFence( + caseUuid ?? "", + ensembleName ?? "", + realizationNum ?? 0, + seismicAttribute ?? "", + timeOrIntervalStr ?? "", + observed ?? false, + bodyPolyline + ), + select: transformSeismicFenceData, + staleTime: STALE_TIME, + cacheTime: CACHE_TIME, + enabled: !!( + allowEnable && + caseUuid && + ensembleName && + realizationNum && + seismicAttribute && + timeOrIntervalStr && + observed && + polyline + ), + }); +} diff --git a/frontend/src/modules/SeismicIntersection/registerModule.ts b/frontend/src/modules/SeismicIntersection/registerModule.ts new file mode 100644 index 000000000..0299b0f3f --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/registerModule.ts @@ -0,0 +1,13 @@ +import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { SyncSettingKey } from "@framework/SyncSettings"; + +// import { preview } from "./preview"; +import { State } from "./state"; + +ModuleRegistry.registerModule({ + moduleName: "SeismicIntersection", + defaultTitle: "Seismic Intersection", + syncableSettingKeys: [SyncSettingKey.ENSEMBLE], + // preview, + description: "Visualization fence of seismic intersection data with a wellbore", +}); diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx new file mode 100644 index 000000000..8a1f38d7b --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/settings.tsx @@ -0,0 +1,115 @@ +import React from "react"; + +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { ModuleFCProps } from "@framework/Module"; +import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; +import { useEnsembleSet } from "@framework/WorkbenchSession"; +import { SingleEnsembleSelect } from "@framework/components/SingleEnsembleSelect"; +import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; +import { ApiStateWrapper } from "@lib/components/ApiStateWrapper"; +import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; +import { Input } from "@lib/components/Input"; +import { Label } from "@lib/components/Label"; + +import { useSeismicCubeDirectoryQuery } from "./queryHooks"; +import { State } from "./state"; +import { SeismicAddress } from "./types"; +import { SeismicCubeDirectory, TimeType } from "./utils/seismicCubeDirectory"; + +export function settings({ moduleContext, workbenchSession, workbenchServices }: ModuleFCProps) { + const syncedSettingKeys = moduleContext.useSyncedSettingKeys(); + const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); + const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); + const ensembleSet = useEnsembleSet(workbenchSession); + + const setSeismicAddress = moduleContext.useSetStoreValue("seismicAddress"); + + const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); + const [realizationNumber, setRealizationNumber] = React.useState(0); + const [isObserved, setIsObserved] = React.useState(false); + const [selectedSeismicAttribute, setSelectedSeismicattribute] = React.useState(null); + const [selectedTime, setSelectedTime] = React.useState(null); + + const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(selectedEnsembleIdent, syncedValueEnsembles); + const computedEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); + if (computedEnsembleIdent && !computedEnsembleIdent.equals(selectedEnsembleIdent)) { + setSelectedEnsembleIdent(computedEnsembleIdent); + } + + const seismicCubeDirectoryQuery = useSeismicCubeDirectoryQuery( + computedEnsembleIdent?.getCaseUuid(), + computedEnsembleIdent?.getEnsembleName() + ); + const seismicCubeDirectory = seismicCubeDirectoryQuery.data + ? new SeismicCubeDirectory({ + seismicCubeMetas: seismicCubeDirectoryQuery.data, + timeType: TimeType.TimePoint, // Convert from 3D/4D to time type? + useObservedSeismicCubes: isObserved, + }) + : null; + + React.useEffect( + function propagateSeismicAddress() { + let seismicAddress: SeismicAddress | null = null; + if (computedEnsembleIdent && selectedSeismicAttribute && selectedTime) { + seismicAddress = { + caseUuid: computedEnsembleIdent.getCaseUuid(), + ensemble: computedEnsembleIdent.getEnsembleName(), + realizationNumber: realizationNumber, + attribute: selectedSeismicAttribute, + timeString: selectedTime, + observed: isObserved, + }; + } + setSeismicAddress(seismicAddress); + }, + [ + computedEnsembleIdent, + selectedSeismicAttribute, + selectedTime, + isObserved, + realizationNumber, + // setSeismicAddress, + ] + ); + + function handleEnsembleSelectionChange(newEnsembleIdent: EnsembleIdent | null) { + setSelectedEnsembleIdent(newEnsembleIdent); + if (newEnsembleIdent) { + syncHelper.publishValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles", [newEnsembleIdent]); + } + } + + function handleRealizationTextChanged(event: React.ChangeEvent) { + const base10 = 10; + const realNum = parseInt(event.target.value, base10); + if (realNum >= 0) { + setRealizationNumber(realNum); + } + } + + return ( +
+ + + + + + {/** + * TODO: + * - Add `isObserved` control -> simulated/observed toggle with radio buttons + * - Seismic survey type -> 3D, Preprocessed 4D or Calculate 4D survey with radio buttons + */} + {/* */} + +
+ ); +} diff --git a/frontend/src/modules/SeismicIntersection/state.ts b/frontend/src/modules/SeismicIntersection/state.ts new file mode 100644 index 000000000..e68cfe8f8 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/state.ts @@ -0,0 +1,8 @@ +import { Wellbore } from "@framework/Wellbore"; + +import { SeismicAddress } from "./types"; + +export interface State { + wellBoreAddress: Wellbore | null; + seismicAddress: SeismicAddress | null; +} diff --git a/frontend/src/modules/SeismicIntersection/types.ts b/frontend/src/modules/SeismicIntersection/types.ts new file mode 100644 index 000000000..5b7aeca32 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/types.ts @@ -0,0 +1,13 @@ +export type SeismicAddress = { + caseUuid: string; + ensemble: string; + realizationNumber: number; + attribute: string; + observed: boolean; + timeString?: string; +}; + +export enum SurveyType { + SEISMIC_3D = "3D", + SEISMIC_4D = "4D", +} diff --git a/frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts b/frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts new file mode 100644 index 000000000..cc640dee8 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts @@ -0,0 +1,22 @@ +import { SeismicFenceData_api } from "@api"; +import { b64DecodeFloatArrayToFloat32 } from "@modules_shared/base64"; + +// Data structure for transformed data +// Remove the base64 encoded data and replace with a Float32Array +export type SeismicFenceData_trans = Omit & { + fenceTracesFloat32Arr: Float32Array; +}; + +export function transformSeismicFenceData(apiData: SeismicFenceData_api): SeismicFenceData_trans { + const startTS = performance.now(); + + const { fence_traces_b64arr, ...untransformedData } = apiData; + const dataFloat32Arr = b64DecodeFloatArrayToFloat32(fence_traces_b64arr); + + console.debug(`transformSurfaceData() took: ${(performance.now() - startTS).toFixed(1)}ms`); + + return { + ...untransformedData, + fenceTracesFloat32Arr: dataFloat32Arr, + }; +} diff --git a/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts b/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts new file mode 100644 index 000000000..f031e2439 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts @@ -0,0 +1,53 @@ +import { SeismicCubeMeta_api, SurfaceAttributeType_api } from "@api"; +import { isIsoStringInterval } from "@framework/utils/timestampUtils"; + +export enum TimeType { + TimePoint = "TimePoint", + Interval = "Interval", +} + +export type SeismicCubeDirectoryOptions = { + seismicCubeMetas: SeismicCubeMeta_api[]; + timeType: TimeType; + useObservedSeismicCubes?: boolean; +}; + +// Class responsible for managing a directory of seismic cubes. +export class SeismicCubeDirectory { + private _seismicCubeList: SeismicCubeMeta_api[] = []; + + // Constructs a SeismicCubeDirectory with optional content filter criteria. + constructor(options: SeismicCubeDirectoryOptions | null) { + if (!options) return; + + let filteredList = filterOnTimeType(options.seismicCubeMetas, options.timeType); + + if (options.useObservedSeismicCubes) { + filteredList = filteredList.filter((cube) => cube.is_observation); + } else { + filteredList = filteredList.filter((cube) => !cube.is_observation); + } + + this._seismicCubeList = filteredList; + } + + public getAttributeNames(): string[] { + return [...new Set(this._seismicCubeList.map((cube) => cube.seismic_attribute))].sort(); + } +} + +// Filters directory based on time type. +function filterOnTimeType(seismicCubeList: SeismicCubeMeta_api[], timeType: TimeType): SeismicCubeMeta_api[] { + switch (timeType) { + case TimeType.TimePoint: + return seismicCubeList.filter( + (cube) => cube.iso_date_or_interval && !isIsoStringInterval(cube.iso_date_or_interval) + ); + case TimeType.Interval: + return seismicCubeList.filter( + (cube) => cube.iso_date_or_interval && isIsoStringInterval(cube.iso_date_or_interval) + ); + default: + throw new Error("Invalid TimeType"); + } +} diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx new file mode 100644 index 000000000..a22f07341 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import { SeismicFencePolyline_api } from "@api"; +import { ModuleFCProps } from "@framework/Module"; + +import { useSeismicFenceDataQuery } from "./queryHooks"; +import { State } from "./state"; + +export const view = ({ moduleContext, workbenchSession, workbenchServices }: ModuleFCProps) => { + const seismicAddress = moduleContext.useStoreValue("seismicAddress"); + + const polyline: SeismicFencePolyline_api = { x_points: [], y_points: [] }; + + const seismicFenceDataQuery = useSeismicFenceDataQuery( + seismicAddress?.caseUuid ?? null, + seismicAddress?.ensemble ?? null, + seismicAddress?.realizationNumber ?? null, + seismicAddress?.attribute ?? null, + seismicAddress?.timeString ?? null, + seismicAddress?.observed ?? null, + polyline, + true + ); + return <>; +}; diff --git a/frontend/src/modules/registerAllModules.ts b/frontend/src/modules/registerAllModules.ts index a808ade96..01118989f 100644 --- a/frontend/src/modules/registerAllModules.ts +++ b/frontend/src/modules/registerAllModules.ts @@ -8,6 +8,7 @@ import "./Grid3DIntersection/registerModule"; import "./InplaceVolumetrics/registerModule"; import "./Map/registerModule"; import "./Pvt/registerModule"; +import "./SeismicIntersection/registerModule"; import "./SimulationTimeSeries/registerModule"; import "./SimulationTimeSeriesMatrix/registerModule"; import "./SimulationTimeSeriesSensitivity/registerModule"; From 353cfd01daf45239c37fe8de0a77e0423a7b6d48 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 26 Oct 2023 09:02:03 +0200 Subject: [PATCH 18/35] Next iteration of front-end code --- backend/seismic_vds_slice.ipynb | 288 +++++++++++++++--- .../backend/primary/routers/seismic/router.py | 14 +- .../primary/routers/seismic/schemas.py | 3 +- backend/src/services/sumo_access/_helpers.py | 6 +- .../src/services/sumo_access/queries/case.py | 13 + backend/src/services/vds_access/vds_access.py | 27 +- .../src/api/models/SeismicFencePolyline.ts | 2 +- frontend/src/modules/Grid3D/queryHooks.ts | 21 -- frontend/src/modules/Grid3D/settings.tsx | 3 +- frontend/src/modules/Grid3D/view.tsx | 8 +- .../SeismicIntersection/loadModule.tsx | 2 +- .../SeismicIntersection/queryHooks.tsx | 10 +- .../modules/SeismicIntersection/settings.tsx | 253 +++++++++++++-- .../src/modules/SeismicIntersection/state.ts | 2 +- .../src/modules/SeismicIntersection/types.ts | 5 - .../utils/esvIntersectionUtils.ts | 176 +++++++++++ .../utils/seismicCubeDirectory.ts | 31 +- .../src/modules/SeismicIntersection/view.tsx | 222 +++++++++++++- .../src/modules/SubsurfaceMap/queryHooks.tsx | 37 +-- .../src/modules/SubsurfaceMap/settings.tsx | 3 +- frontend/src/modules/SubsurfaceMap/view.tsx | 7 +- .../src/modules/_shared/WellBore/index.ts | 1 + .../modules/_shared/WellBore/queryHooks.ts | 36 +++ 23 files changed, 984 insertions(+), 186 deletions(-) create mode 100644 frontend/src/modules/SeismicIntersection/utils/esvIntersectionUtils.ts create mode 100644 frontend/src/modules/_shared/WellBore/index.ts create mode 100644 frontend/src/modules/_shared/WellBore/queryHooks.ts diff --git a/backend/seismic_vds_slice.ipynb b/backend/seismic_vds_slice.ipynb index 34dc05de1..f9e6ee44e 100644 --- a/backend/seismic_vds_slice.ipynb +++ b/backend/seismic_vds_slice.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -11,40 +11,148 @@ "text": [ "axis=[VdsAxis(annotation='Inline', max=217.0, min=0.0, samples=218, unit='unitless'), VdsAxis(annotation='Crossline', max=137.0, min=0.0, samples=138, unit='unitless'), VdsAxis(annotation='Sample', max=1999.0, min=1500.0, samples=500, unit='ms')] boundingBox=VdsBoundingBox(cdp=[[461484.34, 5926563.2], [456083.37, 5936006.48], [462069.5080733945, 5939430.173027524], [467470.4780733945, 5929986.8930275235]], ij=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]], ilxl=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]]) crs=''\n", "Vds Access coordinate system: \n", - "Sumo case coordinate system: ST_WGS84_UTM37N_P32637\n", "Flattened fence traces array: [0. 0. 0. ... 0. 0. 0.]\n", - "Number of traces: 4\n", - "Number of samples in trace: 500\n" + "Number of traces: 10\n", + "Number of samples in trace: 500\n", + "\n", + "\n", + "Number of fence traces: 10\n", + "Length of fence traces: 500\n", + "Shape: Number of traces: 10\n", + "Shape: Number of fence samples: 500\n", + "Fence traces array: [[0. 0. 0. ... 0. 0. 0.]\n", + " [0. 0. 0. ... 0. 0. 0.]\n", + " [0. 0. 0. ... 0. 0. 0.]\n", + " ...\n", + " [0. 0. 0. ... 0. 0. 0.]\n", + " [0. 0. 0. ... 0. 0. 0.]\n", + " [0. 0. 0. ... 0. 0. 0.]]\n", + "First trace: [ 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 -9.2752583e-05 -2.6747005e-04\n", + " -3.7114468e-04 -6.2928506e-04 -1.0021555e-03 -1.5351177e-03\n", + " -2.3295472e-03 -3.4679242e-03 -5.1008533e-03 -7.0881671e-03\n", + " -9.8076593e-03 -1.3284918e-02 -1.7565463e-02 -2.2672666e-02\n", + " -2.8664507e-02 -3.5130404e-02 -4.1960362e-02 -4.8559703e-02\n", + " -5.4532271e-02 -5.9226822e-02 -6.1765946e-02 -6.1519943e-02\n", + " -5.7856992e-02 -5.0333757e-02 -3.8805325e-02 -2.3421533e-02\n", + " -5.0506815e-03 1.5544317e-02 3.6964323e-02 5.7683878e-02\n", + " 7.6133333e-02 9.0877421e-02 1.0078802e-01 1.0518546e-01\n", + " 1.0392365e-01 9.7404234e-02 8.6516015e-02 7.2507888e-02\n", + " 5.6816332e-02 4.0875100e-02 2.5938479e-02 1.2945393e-02\n", + " 2.4442780e-03 -5.4136692e-03 -1.0817782e-02 -1.4193936e-02\n", + " -1.6076926e-02 -1.6985366e-02 -1.7322414e-02 -1.7318489e-02\n", + " -1.7022343e-02 -1.6336577e-02 -1.5085100e-02 -1.3094651e-02\n", + " -1.0271180e-02 -6.6546444e-03 -2.4414081e-03 2.0289721e-03\n", + " 6.3185203e-03 9.9569801e-03 1.2515657e-02 1.3674809e-02\n", + " 1.3272504e-02 1.1327312e-02 8.0323834e-03 3.7238598e-03\n", + " -1.1693260e-03 -6.0602892e-03 -1.0829658e-02 -1.4861319e-02\n", + " -1.7981935e-02 -1.9993670e-02 -2.0909099e-02 -2.0820366e-02\n", + " -1.9884996e-02 -1.8224414e-02 -1.6366670e-02 -1.4108810e-02\n", + " -1.1817116e-02 -9.6391533e-03 -7.6238750e-03 -5.8751642e-03\n", + " -4.5101382e-03 -3.2928942e-03 -2.4548089e-03 -1.7266531e-03\n", + " -1.1366470e-03 -8.2339306e-04 -5.0562772e-04 -3.1820493e-04\n", + " -2.1667115e-04 -1.4869146e-04 -1.4121966e-04 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", + " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00]\n" ] - }, - { - "data": { - "text/plain": [ - "[['t11', 't12', 't13'], ['t21', 't22', 't23'], ['t31', 't32', 't33']]" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "array(['t11', 't12', 't13', 't21', 't22', 't23', 't31', 't32', 't33'],\n", - " dtype=' schemas.SeismicFenceData: """Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. @@ -70,8 +70,9 @@ async def get_fence( authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name ) + vds_handle: Optional[VdsHandle] = None try: - vds_handle: VdsHandle = seismic_access.get_vds_handle( + vds_handle: VdsHandle = await seismic_access.get_vds_handle( realization=realization_num, seismic_attribute=seismic_attribute, time_or_interval_str=time_or_interval_str, @@ -80,6 +81,9 @@ async def get_fence( except ValueError as err: raise HTTPException(status_code=404, detail=str(err)) from err + if vds_handle is None: + raise HTTPException(status_code=404, detail="Vds handle not found") + vds_access = VdsAccess(sas_token=vds_handle.sas_token, vds_url=vds_handle.vds_url) # Retrieve fence and post as seismic intersection using cdp coordinates for vds-slice @@ -102,6 +106,6 @@ async def get_fence( fence_traces_b64arr=b64_encode_float_array_as_float32(flattened_fence_traces_array), num_traces=num_traces, num_trace_samples=num_trace_samples, - min_fence_depth=depth_axis_meta.min, # TODO: Should this be depth_axis_meta.max? - max_fence_depth=depth_axis_meta.max, # TODO: Should this be depth_axis_meta.min? + min_fence_depth=depth_axis_meta.min, + max_fence_depth=depth_axis_meta.max, ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 1541c4746..d450efacf 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -22,13 +22,12 @@ class SeismicFencePolyline(BaseModel): NOTE: - Verify coordinates are in domain coordinate system (UTM)? - - Consider points_xy array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? + - Consider points_xy: List[float] - i.e. array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? - Ensure equal length of x_points and y_points arrays? """ x_points: List[float] y_points: List[float] - # points_xy: List[float] class SeismicFenceData(BaseModel): diff --git a/backend/src/services/sumo_access/_helpers.py b/backend/src/services/sumo_access/_helpers.py index d5d06fc4e..4357f3d6b 100644 --- a/backend/src/services/sumo_access/_helpers.py +++ b/backend/src/services/sumo_access/_helpers.py @@ -4,7 +4,7 @@ from fmu.sumo.explorer.objects import CaseCollection, Case from src import config -from .queries.case import get_stratigraphic_column_identifier, get_field_identifiers +from .queries.case import get_stratigraphic_column_identifier, get_field_identifiers, get_coordinate_system_identifier def create_sumo_client_instance(access_token: str) -> SumoClient: @@ -47,6 +47,10 @@ async def get_field_identifiers(self) -> List[str]: """Retrieve the field identifiers for a case""" return await get_field_identifiers(self._sumo_client, self._case_uuid) + async def get_coordinate_system_identifier(self) -> str: + """Retrieve the coordinate system identifier for a case""" + return await get_coordinate_system_identifier(self._sumo_client, self._case_uuid) + class SumoEnsemble(SumoCase): def __init__(self, sumo_client: SumoClient, case: Case, case_uuid: str, iteration_name: str): diff --git a/backend/src/services/sumo_access/queries/case.py b/backend/src/services/sumo_access/queries/case.py index 6c5ee1455..d334a47d8 100644 --- a/backend/src/services/sumo_access/queries/case.py +++ b/backend/src/services/sumo_access/queries/case.py @@ -28,3 +28,16 @@ async def get_field_identifiers(sumo_client: SumoClient, case_id: str) -> List[s hits = response["hits"]["hits"] fields = hits[0]["_source"]["masterdata"]["smda"]["field"] return [field["identifier"] for field in fields] + + +async def get_coordinate_system_identifier(sumo_client: SumoClient, case_id: str) -> str: + """Get coordunate syte identifier for a case (assuming unique for all objects)""" + response = await sumo_client.get_async( + "/search", + query=f"_sumo.parent_object:{case_id}", + size=1, + select="masterdata.smda.coordinate_system.identifier", + ) + + hits = response["hits"]["hits"] + return hits[0]["_source"]["masterdata"]["smda"]["coordinate_system"]["identifier"] diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 305e61e11..1168e400c 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -25,10 +25,7 @@ def bytes_to_ndarray_float32(bytes_data: bytes, shape: List[int]) -> NDArray[np.float32]: """ - Convert bytes to numpy ndarray with specified shape and "C" order - - NOTE: Need for order in this function? - + Convert bytes to numpy ndarray with row-major order, i.e. "C" order """ return np.ndarray(shape=shape, dtype=" NDA """ Convert bytes to numpy flatten ndarray with row-major order, i.e. "C" order """ - return np.ndarray(shape=shape, dtype=" None: async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response: """Query the service""" + VDS_HOST_ADDRESS = "https://server-oneseismictest-dev.playground.radix.equinor.com" + async with httpx.AsyncClient() as client: response = await client.post( f"{VDS_HOST_ADDRESS}/{endpoint}", @@ -127,9 +126,9 @@ async def get_flattened_fence_traces_array_and_metadata( trace_1 trace_2 trace_m |--------|--- ... ---| sample_1 |--------|--- ... ---| sample_2 - . - . - . + . + . + . |--------|--- ... ---| sample_n-1 |--------|--- ... ---| sample_n ``` @@ -171,12 +170,12 @@ async def get_flattened_fence_traces_array_and_metadata( if len(metadata.shape) != 2: raise ValueError(f"Expected shape to be 2D, got {metadata.shape}") - # TODO: Drop and just provide flattened straight away? - fence_traces_ndarray_float32 = bytes_to_ndarray_float32(byte_array, shape=metadata.shape) + # fence array data: [[t11, t12, ..., t1n], [t21, t22, ..., t2n], ..., [tm1, tm2, ..., tmn]] + # m = num_traces, n = num_trace_samples + num_traces = metadata.shape[0] + num_trace_samples = metadata.shape[1] # Flattened array with row major order, i.e. C-order in numpy - flattened_fence_traces_float32_array = bytes_to_flatten_ndarray_float32( - fence_traces_ndarray_float32, shape=fence_traces_ndarray_float32.shape - ) + flattened_fence_traces_float32_array = bytes_to_flatten_ndarray_float32(byte_array, shape=metadata.shape) - return (flattened_fence_traces_float32_array, metadata.shape[0], metadata.shape[1]) + return (flattened_fence_traces_float32_array, num_traces, num_trace_samples) diff --git a/frontend/src/api/models/SeismicFencePolyline.ts b/frontend/src/api/models/SeismicFencePolyline.ts index 70027385d..e0cd9ad37 100644 --- a/frontend/src/api/models/SeismicFencePolyline.ts +++ b/frontend/src/api/models/SeismicFencePolyline.ts @@ -11,7 +11,7 @@ * * NOTE: * - Verify coordinates are in domain coordinate system (UTM)? - * - Consider points_xy array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? + * - Consider points_xy: List[float] - i.e. array with [x1, y1, x2, y2, ..., xn, yn] instead of x_points and y_points arrays? * - Ensure equal length of x_points and y_points arrays? */ export type SeismicFencePolyline = { diff --git a/frontend/src/modules/Grid3D/queryHooks.ts b/frontend/src/modules/Grid3D/queryHooks.ts index 0fc0c29ca..238ebde85 100644 --- a/frontend/src/modules/Grid3D/queryHooks.ts +++ b/frontend/src/modules/Grid3D/queryHooks.ts @@ -1,4 +1,3 @@ -import { WellBoreHeader_api, WellBoreTrajectory_api } from "@api"; import { apiService } from "@framework/ApiService"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; @@ -95,23 +94,3 @@ export function useGridParameterNames( enabled: caseUuid && ensembleName && gridName ? true : false, }); } - -export function useGetWellHeaders(caseUuid: string | undefined): UseQueryResult { - return useQuery({ - queryKey: ["getWellHeaders", caseUuid], - queryFn: () => apiService.well.getWellHeaders(caseUuid ?? ""), - staleTime: STALE_TIME, - cacheTime: STALE_TIME, - enabled: caseUuid ? true : false, - }); -} - -export function useGetFieldWellsTrajectories(caseUuid: string | undefined): UseQueryResult { - return useQuery({ - queryKey: ["getFieldWellsTrajectories", caseUuid], - queryFn: () => apiService.well.getFieldWellTrajectories(caseUuid ?? ""), - staleTime: STALE_TIME, - cacheTime: CACHE_TIME, - enabled: caseUuid ? true : false, - }); -} diff --git a/frontend/src/modules/Grid3D/settings.tsx b/frontend/src/modules/Grid3D/settings.tsx index 5e63512ff..832af05b9 100644 --- a/frontend/src/modules/Grid3D/settings.tsx +++ b/frontend/src/modules/Grid3D/settings.tsx @@ -12,8 +12,9 @@ import { CircularProgress } from "@lib/components/CircularProgress"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { Label } from "@lib/components/Label"; import { Select, SelectOption } from "@lib/components/Select"; +import { useGetWellHeaders } from "@modules/_shared/WellBore/queryHooks"; -import { useGetWellHeaders, useGridModelNames, useGridParameterNames } from "./queryHooks"; +import { useGridModelNames, useGridParameterNames } from "./queryHooks"; import state from "./state"; //----------------------------------------------------------------------------------------------------------- diff --git a/frontend/src/modules/Grid3D/view.tsx b/frontend/src/modules/Grid3D/view.tsx index f127ce27c..7f0487b62 100644 --- a/frontend/src/modules/Grid3D/view.tsx +++ b/frontend/src/modules/Grid3D/view.tsx @@ -9,15 +9,11 @@ import { createWellBoreHeaderLayer, createWellboreTrajectoryLayer, } from "@modules/SubsurfaceMap/_utils"; +import { useGetFieldWellsTrajectories } from "@modules/_shared/WellBore/queryHooks"; import SubsurfaceViewer from "@webviz/subsurface-viewer"; import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; -import { - useGetFieldWellsTrajectories, - useGridParameter, - useGridSurface, - useStatisticalGridParameter, -} from "./queryHooks"; +import { useGridParameter, useGridSurface, useStatisticalGridParameter } from "./queryHooks"; import state from "./state"; //----------------------------------------------------------------------------------------------------------- diff --git a/frontend/src/modules/SeismicIntersection/loadModule.tsx b/frontend/src/modules/SeismicIntersection/loadModule.tsx index d82514593..c181bfc26 100644 --- a/frontend/src/modules/SeismicIntersection/loadModule.tsx +++ b/frontend/src/modules/SeismicIntersection/loadModule.tsx @@ -5,7 +5,7 @@ import { State } from "./state"; import { view } from "./view"; const defaultState: State = { - wellBoreAddress: { uwi: "55/33-A-4", uuid: "drogon_horizontal", type: "smda" }, + wellboreAddress: { uwi: "55/33-A-4", uuid: "drogon_horizontal", type: "smda" }, seismicAddress: null, // viewSettings: { // showGridParameter: false, diff --git a/frontend/src/modules/SeismicIntersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx index ff2c2e8df..0454d87e0 100644 --- a/frontend/src/modules/SeismicIntersection/queryHooks.tsx +++ b/frontend/src/modules/SeismicIntersection/queryHooks.tsx @@ -16,7 +16,7 @@ export function useSeismicCubeDirectoryQuery( queryFn: () => apiService.seismic.getSeismicDirectory(caseUuid ?? "", ensembleName ?? ""), staleTime: STALE_TIME, cacheTime: CACHE_TIME, - enabled: caseUuid && ensembleName ? true : false, + enabled: !!(caseUuid && ensembleName), }); } @@ -40,7 +40,7 @@ export function useSeismicFenceDataQuery( seismicAttribute, timeOrIntervalStr, observed, - polyline, + bodyPolyline, ], queryFn: () => apiService.seismic.getFence( @@ -59,11 +59,11 @@ export function useSeismicFenceDataQuery( allowEnable && caseUuid && ensembleName && - realizationNum && + realizationNum !== null && seismicAttribute && timeOrIntervalStr && - observed && - polyline + observed !== null && + polyline !== null ), }); } diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx index 8a1f38d7b..9a201fd6d 100644 --- a/frontend/src/modules/SeismicIntersection/settings.tsx +++ b/frontend/src/modules/SeismicIntersection/settings.tsx @@ -2,33 +2,63 @@ import React from "react"; import { EnsembleIdent } from "@framework/EnsembleIdent"; import { ModuleFCProps } from "@framework/Module"; +import { useSettingsStatusWriter } from "@framework/StatusWriter"; import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; +import { Wellbore } from "@framework/Wellbore"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { SingleEnsembleSelect } from "@framework/components/SingleEnsembleSelect"; import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; import { ApiStateWrapper } from "@lib/components/ApiStateWrapper"; +import { CircularProgress } from "@lib/components/CircularProgress"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { Input } from "@lib/components/Input"; import { Label } from "@lib/components/Label"; +import { RadioGroup } from "@lib/components/RadioGroup"; +import { Select, SelectOption } from "@lib/components/Select"; +import { useValidState } from "@lib/hooks/useValidState"; +import { useGetWellHeaders } from "@modules/_shared/WellBore"; + +import { isEqual } from "lodash"; import { useSeismicCubeDirectoryQuery } from "./queryHooks"; import { State } from "./state"; import { SeismicAddress } from "./types"; import { SeismicCubeDirectory, TimeType } from "./utils/seismicCubeDirectory"; +const TimeTypeEnumToSurveyTypeStringMapping = { + [TimeType.TimePoint]: "3D", + [TimeType.Interval]: "4D", +}; +const TimeTypeEnumToSeismicTimeTypeStringMapping = { + [TimeType.TimePoint]: "Seismic timestamps", + [TimeType.Interval]: "Seismic intervals", +}; +const enum SeismicDataSource { + //TODO: Find better name of enum? + SIMULATED = "Simulated", + OBSERVED = "Observed", +} + export function settings({ moduleContext, workbenchSession, workbenchServices }: ModuleFCProps) { const syncedSettingKeys = moduleContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); const ensembleSet = useEnsembleSet(workbenchSession); + const statusWriter = useSettingsStatusWriter(moduleContext); + + const wellboreType = "smda"; const setSeismicAddress = moduleContext.useSetStoreValue("seismicAddress"); + const setWellboreAddress = moduleContext.useSetStoreValue("wellboreAddress"); const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); const [realizationNumber, setRealizationNumber] = React.useState(0); const [isObserved, setIsObserved] = React.useState(false); - const [selectedSeismicAttribute, setSelectedSeismicattribute] = React.useState(null); - const [selectedTime, setSelectedTime] = React.useState(null); + + const [surveyTimeType, setSurveyTimeType] = React.useState(TimeType.TimePoint); + const [selectedWellboreAddress, setSelectedWellboreAddress] = React.useState( + moduleContext.useStoreValue("wellboreAddress") + ); const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(selectedEnsembleIdent, syncedValueEnsembles); const computedEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); @@ -36,20 +66,66 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: setSelectedEnsembleIdent(computedEnsembleIdent); } + // Queries + const wellHeadersQuery = useGetWellHeaders(computedEnsembleIdent?.getCaseUuid()); const seismicCubeDirectoryQuery = useSeismicCubeDirectoryQuery( computedEnsembleIdent?.getCaseUuid(), computedEnsembleIdent?.getEnsembleName() ); + if (wellHeadersQuery.isError) { + statusWriter.addError("Error loading well headers"); + } + if (seismicCubeDirectoryQuery.isError) { + statusWriter.addError("Error loading seismic directory"); + } + + // Handling well headers query + const syncedWellBore = syncHelper.useValue(SyncSettingKey.WELLBORE, "global.syncValue.wellBore"); + const availableWellboreList: Wellbore[] = + wellHeadersQuery.data?.map((wellbore) => ({ + type: wellboreType, + uwi: wellbore.unique_wellbore_identifier, + uuid: wellbore.wellbore_uuid, + })) || []; + const computedWellboreAddress = fixupSyncedOrSelectedOrFirstWellbore( + syncedWellBore || null, + selectedWellboreAddress || null, + availableWellboreList + ); + + if (!isEqual(computedWellboreAddress, selectedWellboreAddress)) { + setSelectedWellboreAddress(computedWellboreAddress); + } + + // Handling seismic cube directory query const seismicCubeDirectory = seismicCubeDirectoryQuery.data ? new SeismicCubeDirectory({ - seismicCubeMetas: seismicCubeDirectoryQuery.data, - timeType: TimeType.TimePoint, // Convert from 3D/4D to time type? + seismicCubeMetaArray: seismicCubeDirectoryQuery.data, + timeType: surveyTimeType, useObservedSeismicCubes: isObserved, }) : null; + const [selectedSeismicAttribute, setSelectedSeismicAttribute] = useValidState( + null, + seismicCubeDirectory?.getAttributeNames() ?? [] + ); + const [selectedTime, setSelectedTime] = useValidState( + null, + seismicCubeDirectory?.getTimeOrIntervalStrings() ?? [] + ); + + const seismicAttributeOptions = seismicCubeDirectory + ? seismicCubeDirectory.getAttributeNames().map((attribute) => { + return { label: attribute, value: attribute }; + }) + : []; + const timeOptions = seismicCubeDirectory + ? createOptionsFromTimeOrIntervalStrings(seismicCubeDirectory.getTimeOrIntervalStrings()) + : []; + React.useEffect( - function propagateSeismicAddress() { + function propagateSeismicAddressToView() { let seismicAddress: SeismicAddress | null = null; if (computedEnsembleIdent && selectedSeismicAttribute && selectedTime) { seismicAddress = { @@ -63,14 +139,14 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: } setSeismicAddress(seismicAddress); }, - [ - computedEnsembleIdent, - selectedSeismicAttribute, - selectedTime, - isObserved, - realizationNumber, - // setSeismicAddress, - ] + [computedEnsembleIdent, selectedSeismicAttribute, selectedTime, isObserved, realizationNumber] + ); + + React.useEffect( + function propagateWellBoreAddressToView() { + setWellboreAddress(selectedWellboreAddress); + }, + [selectedWellboreAddress] ); function handleEnsembleSelectionChange(newEnsembleIdent: EnsembleIdent | null) { @@ -88,9 +164,42 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: } } + function handleSeismicAttributeChange(values: string[]) { + if (values.length === 0) { + setSelectedSeismicAttribute(null); + return; + } + setSelectedSeismicAttribute(values[0]); + } + + function handleSeismicTimeChange(values: string[]) { + if (values.length === 0) { + setSelectedTime(null); + return; + } + setSelectedTime(values[0]); + } + + function handleWellChange(selectedWellboreUuids: string[], validWellboreList: Wellbore[]) { + if (selectedWellboreUuids.length === 0) { + setSelectedWellboreAddress(null); + return; + } + + // Use only first wellbore + const wellboreUuid = selectedWellboreUuids[0]; + const wellUwi = validWellboreList.find((wellbore) => wellbore.uuid === wellboreUuid)?.uwi; + + if (!wellUwi) return; + + const newWellboreAddress: Wellbore = { type: wellboreType, uuid: wellboreUuid, uwi: wellUwi }; + setSelectedWellboreAddress(newWellboreAddress); + syncHelper.publishValue(SyncSettingKey.WELLBORE, "global.syncValue.wellBore", newWellboreAddress); + } + return ( -
- +
+ + + } + > + + + +
); } diff --git a/frontend/src/modules/SeismicIntersection/state.ts b/frontend/src/modules/Intersection/state.ts similarity index 82% rename from frontend/src/modules/SeismicIntersection/state.ts rename to frontend/src/modules/Intersection/state.ts index 9330fdd54..1a84af8e1 100644 --- a/frontend/src/modules/SeismicIntersection/state.ts +++ b/frontend/src/modules/Intersection/state.ts @@ -5,4 +5,6 @@ import { SeismicAddress } from "./types"; export interface State { wellboreAddress: Wellbore | null; seismicAddress: SeismicAddress | null; + extension: number; + zScale: number; } diff --git a/frontend/src/modules/SeismicIntersection/types.ts b/frontend/src/modules/Intersection/types.ts similarity index 100% rename from frontend/src/modules/SeismicIntersection/types.ts rename to frontend/src/modules/Intersection/types.ts diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionUtils.ts b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts similarity index 70% rename from frontend/src/modules/SeismicIntersection/utils/esvIntersectionUtils.ts rename to frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts index 4416e2a16..8aabf138f 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionUtils.ts +++ b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts @@ -4,49 +4,11 @@ import { IntersectionReferenceSystem, OverlayMouseMoveEvent, SeismicCanvasLayer, - Trajectory, WellborepathLayer, getSeismicInfo, getSeismicOptions, } from "@equinor/esv-intersection"; -/** - * Utility to make extended trajectory object from wellbore trajectory - */ -export function makeExtendedTrajectory(wellboreTrajectory: WellBoreTrajectory_api, extension: number): Trajectory { - const eastingArr = wellboreTrajectory.easting_arr; - const northingArr = wellboreTrajectory.northing_arr; - const tvdArr = wellboreTrajectory.tvd_msl_arr; - const trajectory = eastingArr.map((easting: number, idx: number) => [ - parseFloat(easting.toFixed(3)), - parseFloat(northingArr[idx].toFixed(3)), - parseFloat(tvdArr[idx].toFixed(3)), - ]); - if (eastingArr[0] == eastingArr[eastingArr.length - 1] && northingArr[0] == northingArr[northingArr.length - 1]) { - const addcoordatstart = eastingArr[0] - 100; - const addcoordatend = eastingArr[eastingArr.length - 1] + 100; - const addcoordatstart2 = northingArr[0] - 100; - const addcoordatend2 = northingArr[northingArr.length - 1] + 100; - const firstzcoord = tvdArr[0]; - const lastzcoord = tvdArr[tvdArr.length - 1]; - - trajectory.unshift([addcoordatstart, addcoordatstart2, firstzcoord]); - trajectory.push([addcoordatend, addcoordatend2, lastzcoord]); - } - - const referenceSystem = new IntersectionReferenceSystem(trajectory); - referenceSystem.offset = trajectory[0][2]; // Offset should be md at start of path - - const displacement = referenceSystem.displacement || 1; - // Number of samples. Needs some thought. - const samplingIncrement = 5; //meters - const steps = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrement)); - console.debug("Number of samples for intersection ", steps); - const traj = referenceSystem.getExtendedTrajectory(steps, extension, extension); - traj.points = traj.points.map((point) => [parseFloat(point[0].toFixed(3)), parseFloat(point[1].toFixed(3))]); - return traj; -} - /** * Utility to add md overlay for hover to esv intersection controller */ diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts new file mode 100644 index 000000000..d3d46843f --- /dev/null +++ b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts @@ -0,0 +1,98 @@ +import { WellBoreTrajectory_api } from "@api"; +import { IntersectionReferenceSystem, Trajectory } from "@equinor/esv-intersection"; + +import { SeismicFenceData_trans } from "./queryDataTransforms"; + +/** + * Utility to make extended trajectory object from wellbore trajectory and extension + */ +export function makeExtendedTrajectoryFromWellboreTrajectory( + wellboreTrajectory: WellBoreTrajectory_api, + extension: number +): Trajectory { + const eastingArr = wellboreTrajectory.easting_arr; + const northingArr = wellboreTrajectory.northing_arr; + const tvdArr = wellboreTrajectory.tvd_msl_arr; + const trajectory = eastingArr.map((easting: number, idx: number) => [ + parseFloat(easting.toFixed(3)), + parseFloat(northingArr[idx].toFixed(3)), + parseFloat(tvdArr[idx].toFixed(3)), + ]); + if (eastingArr[0] == eastingArr[eastingArr.length - 1] && northingArr[0] == northingArr[northingArr.length - 1]) { + const addcoordatstart = eastingArr[0] - 100; + const addcoordatend = eastingArr[eastingArr.length - 1] + 100; + const addcoordatstart2 = northingArr[0] - 100; + const addcoordatend2 = northingArr[northingArr.length - 1] + 100; + const firstzcoord = tvdArr[0]; + const lastzcoord = tvdArr[tvdArr.length - 1]; + + trajectory.unshift([addcoordatstart, addcoordatstart2, firstzcoord]); + trajectory.push([addcoordatend, addcoordatend2, lastzcoord]); + } + + const referenceSystem = new IntersectionReferenceSystem(trajectory); + referenceSystem.offset = trajectory[0][2]; // Offset should be md at start of path + + const displacement = referenceSystem.displacement || 1; + // Number of samples. Needs some thought. + const samplingIncrement = 5; //meters + const steps = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrement)); + console.debug("Number of samples for intersection ", steps); + const traj = referenceSystem.getExtendedTrajectory(steps, extension, extension); + traj.points = traj.points.map((point) => [parseFloat(point[0].toFixed(3)), parseFloat(point[1].toFixed(3))]); + return traj; +} + +/** + * Utility function to convert the 1D array of values from the fence data to a 2D array of values + * for the seismic slice image. + * + * For the bit map image, the values are provided s.t. a seismic trace is a column in the image, + * thus the data will be transposed. + * + * trace a,b,c and d + * + * num_traces = 4 + * num_samples = 3 + * fence_traces = [a1, a2, a3, b1, b2, b3, c1, c2, c3, d1, d2, d3] + * + * Image: + * + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ +export function createSeismicSliceImageDataArrayFromFenceData(fenceData: SeismicFenceData_trans): number[][] { + const imageArray: number[][] = []; + + const numTraces = fenceData.num_traces; + const numSamples = fenceData.num_trace_samples; + const fenceValues = fenceData.fenceTracesFloat32Arr; + + for (let i = 0; i < numSamples; ++i) { + const row: number[] = []; + for (let j = 0; j < numTraces; ++j) { + const index = i + j * numSamples; + row.push(fenceValues[index]); + } + imageArray.push(row); + } + return imageArray; +} + +/** + * Utility to create an array of values for the Y axis of the seismic slice image. I.e. depth values + * for the seismic depth axis. + */ +export function createSeismicSliceImageYAxisValuesArrayFromFenceData(fenceData: SeismicFenceData_trans): number[] { + const yAxisValues: number[] = []; + + const numSamples = fenceData.num_trace_samples; + const minDepth = fenceData.min_fence_depth; + const maxDepth = fenceData.max_fence_depth; + + for (let i = 0; i < numSamples; ++i) { + yAxisValues.push(minDepth + ((maxDepth - minDepth) / numSamples) * i); + } + return yAxisValues; +} diff --git a/frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts b/frontend/src/modules/Intersection/utils/queryDataTransforms.ts similarity index 100% rename from frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts rename to frontend/src/modules/Intersection/utils/queryDataTransforms.ts diff --git a/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts b/frontend/src/modules/Intersection/utils/seismicCubeDirectory.ts similarity index 100% rename from frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts rename to frontend/src/modules/Intersection/utils/seismicCubeDirectory.ts diff --git a/frontend/src/modules/Intersection/view.tsx b/frontend/src/modules/Intersection/view.tsx new file mode 100644 index 000000000..3cf217115 --- /dev/null +++ b/frontend/src/modules/Intersection/view.tsx @@ -0,0 +1,224 @@ +import React, { useId } from "react"; + +import { SeismicFencePolyline_api, WellBoreTrajectory_api } from "@api"; +import { + Controller, + GridLayer, + IntersectionReferenceSystem, + Trajectory, + generateSeismicSliceImage, +} from "@equinor/esv-intersection"; +import { ModuleFCProps } from "@framework/Module"; +import { useViewStatusWriter } from "@framework/StatusWriter"; +import { useElementSize } from "@lib/hooks/useElementSize"; +import { ColorScaleGradientType } from "@lib/utils/ColorScale"; +import { useGetWellTrajectories } from "@modules/_shared/WellBore/queryHooks"; +import { ContentError } from "@modules/_shared/components/ContentMessage"; + +import { isEqual } from "lodash"; + +import { useSeismicFenceDataQuery } from "./queryHooks"; +import { State } from "./state"; +import { + addMDOverlay, + addSeismicLayer, + addWellborePathLayerAndSetReferenceSystem, +} from "./utils/esvIntersectionControllerUtils"; +import { + createSeismicSliceImageDataArrayFromFenceData, + createSeismicSliceImageYAxisValuesArrayFromFenceData, + makeExtendedTrajectoryFromWellboreTrajectory, +} from "./utils/esvIntersectionDataConversion"; + +export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { + const wrapperDivRef = React.useRef(null); + const wrapperDivSize = useElementSize(wrapperDivRef); + const esvIntersectionContainerRef = React.useRef(null); + const esvIntersectionControllerRef = React.useRef(null); + const [extendedWellboreTrajectory, setExtendedWellboreTrajectory] = React.useState(null); + + const gridLayerUuid = useId(); + const statusWriter = useViewStatusWriter(moduleContext); + + const seismicAddress = moduleContext.useStoreValue("seismicAddress"); + const wellboreAddress = moduleContext.useStoreValue("wellboreAddress"); + const extension = moduleContext.useStoreValue("extension"); + const zScale = moduleContext.useStoreValue("zScale"); + + const seismicColorScale = workbenchSettings.useDiscreteColorScale({ + gradientType: ColorScaleGradientType.Diverging, + }); + const seismicColors = seismicColorScale.getColorPalette().getColors(); + + // Data for well trajectory layer in esv-intersection (to be in synch with seismic fence layer) + const [renderWellboreTrajectory, setRenderWellboreTrajectory] = React.useState(null); + + // Data for seismic fence layer in esv-intersection + const [seismicFencePolyline, setSeismicFencePolyline] = React.useState(null); + const [wellboreTrajectoryProjection, setWellboreTrajectoryProjection] = React.useState(null); + const [seismicImageDataArray, setSeismicImageDataArray] = React.useState(null); + const [seismicImageYAxisValues, setSeismicImageYAxisValues] = React.useState(null); + const [seismicFenceImageBitmapAndStatus, setSeismicFenceImageBitmapAndStatus] = React.useState<{ + image: ImageBitmap | null; + errorStatus: boolean; + }>({ image: null, errorStatus: false }); + + React.useEffect(function initializeEsvIntersectionController() { + if (esvIntersectionContainerRef.current) { + const axisOptions = { xLabel: "x", yLabel: "y", unitOfMeasure: "m" }; + esvIntersectionControllerRef.current = new Controller({ + container: esvIntersectionContainerRef.current, + axisOptions, + }); + + // Initialize/configure controller + addMDOverlay(esvIntersectionControllerRef.current); + esvIntersectionControllerRef.current.addLayer(new GridLayer(gridLayerUuid)); + esvIntersectionControllerRef.current.setBounds([10, 1000], [0, 3000]); + esvIntersectionControllerRef.current.setViewport(1000, 1650, 6000); + esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; // viewSettings.zScale + } + return () => { + console.debug("controller destroyed"); + esvIntersectionControllerRef.current?.destroy(); + }; + }, []); + + // Get well trajectories query + const getWellTrajectoriesQuery = useGetWellTrajectories(wellboreAddress ? [wellboreAddress.uuid] : undefined); + if (getWellTrajectoriesQuery.isError) { + statusWriter.addError("Error loading well trajectories"); + } + + // Use first trajectory and create polyline for seismic fence query, and extended wellbore trajectory for generating seismic fence image + if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { + const newExtendedWellboreTrajectory = makeExtendedTrajectoryFromWellboreTrajectory( + getWellTrajectoriesQuery.data[0], + extension + ); + + const x_points = newExtendedWellboreTrajectory + ? newExtendedWellboreTrajectory.points.map((coord) => coord[0]) + : []; + const y_points = newExtendedWellboreTrajectory + ? newExtendedWellboreTrajectory.points.map((coord) => coord[1]) + : []; + + if (!isEqual(newExtendedWellboreTrajectory, extendedWellboreTrajectory)) { + setExtendedWellboreTrajectory(newExtendedWellboreTrajectory); + setSeismicFencePolyline({ x_points, y_points }); + } + + // When new well trajectory is loaded, update the renderWellboreTrajectory and clear the seismic fence image + if (!isEqual(getWellTrajectoriesQuery.data[0], renderWellboreTrajectory)) { + setRenderWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: false }); + } + } + + // Get seismic fence data from polyline + const seismicFenceDataQuery = useSeismicFenceDataQuery( + seismicAddress?.caseUuid ?? null, + seismicAddress?.ensemble ?? null, + seismicAddress?.realizationNumber ?? null, + seismicAddress?.attribute ?? null, + seismicAddress?.timeString ?? null, + seismicAddress?.observed ?? null, + seismicFencePolyline, + seismicAddress !== null + ); + if (seismicFenceDataQuery.isError) { + statusWriter.addError("Error loading seismic fence data"); + } + + // Regenerate seismic fence image when fence data changes + // - Must be useEffect due to async generateSeismicSliceImage function + // - seismicFenceDataQuery.data in dependency array: Assumes provides same reference as long as the query data is the same (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) + React.useEffect( + function generateSeismicFenceImageLayerData() { + if (!seismicFenceDataQuery.data) return; + + // Curtain projection on a set of points in 3D + const newWellboreTrajectoryProjection: number[][] | null = extendedWellboreTrajectory + ? IntersectionReferenceSystem.toDisplacement( + extendedWellboreTrajectory.points, + extendedWellboreTrajectory.offset + ) + : null; + + const newSeismicImageDataArray = createSeismicSliceImageDataArrayFromFenceData(seismicFenceDataQuery.data); + const newSeismicImageYAxisValues = createSeismicSliceImageYAxisValuesArrayFromFenceData( + seismicFenceDataQuery.data + ); + + const imageDataPoints = newSeismicImageDataArray; + const yAxisValues = newSeismicImageYAxisValues; + const trajectory = newWellboreTrajectoryProjection ?? []; + + // Note: useQuery has cache, this does not - thereby the image is regenerated when switching back and forth + generateSeismicSliceImage( + { datapoints: imageDataPoints, yAxisValues: yAxisValues }, + trajectory, + seismicColors, + { + isLeftToRight: true, + } + ) + .then((image) => setSeismicFenceImageBitmapAndStatus({ image: image ?? null, errorStatus: false })) + .catch((_error) => setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: true })); + + setWellboreTrajectoryProjection(newWellboreTrajectoryProjection); + setSeismicImageDataArray(newSeismicImageDataArray); + setSeismicImageYAxisValues(newSeismicImageYAxisValues); + setRenderWellboreTrajectory( + getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0 + ? getWellTrajectoriesQuery.data[0] + : null + ); + }, + [seismicFenceDataQuery.data, extendedWellboreTrajectory] + ); + + if (esvIntersectionControllerRef.current && renderWellboreTrajectory) { + esvIntersectionControllerRef.current.removeAllLayers(); + esvIntersectionControllerRef.current.clearAllData(); + + addWellborePathLayerAndSetReferenceSystem(esvIntersectionControllerRef.current, renderWellboreTrajectory); + + if ( + seismicImageDataArray && + seismicImageYAxisValues && + seismicFenceImageBitmapAndStatus.image && + wellboreTrajectoryProjection + ) { + addSeismicLayer(esvIntersectionControllerRef.current, { + curtain: wellboreTrajectoryProjection, + extension: extension, + image: seismicFenceImageBitmapAndStatus.image, + dataValues: seismicImageDataArray, + yAxisValues: seismicImageYAxisValues, + }); + } + + esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; + esvIntersectionControllerRef.current.adjustToSize( + Math.max(0, wrapperDivSize.width), + Math.max(0, wrapperDivSize.height - 100) + ); + } + + statusWriter.setLoading(getWellTrajectoriesQuery.isFetching || seismicFenceDataQuery.isFetching); + return ( +
+ {seismicFenceDataQuery.isError && getWellTrajectoriesQuery.isError ? ( + Error loading well trajectories and seismic fence data + ) : seismicFenceDataQuery.isError ? ( + Error loading seismic fence data + ) : getWellTrajectoriesQuery.isError ? ( + Error loading well trajectories + ) : ( +
+ )} +
+ ); +}; diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx deleted file mode 100644 index fee4a56bf..000000000 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import React, { useId } from "react"; - -import { SeismicFencePolyline_api } from "@api"; -import { - Controller, - GridLayer, - IntersectionReferenceSystem, - Trajectory, - generateSeismicSliceImage, -} from "@equinor/esv-intersection"; -import { ModuleFCProps } from "@framework/Module"; -import { useViewStatusWriter } from "@framework/StatusWriter"; -import { useElementSize } from "@lib/hooks/useElementSize"; -import { ColorScaleGradientType } from "@lib/utils/ColorScale"; -import { useGetWellTrajectories } from "@modules/_shared/WellBore/queryHooks"; -import { ContentError } from "@modules/_shared/components/ContentMessage"; - -import { useSeismicFenceDataQuery } from "./queryHooks"; -import { State } from "./state"; -import { - addMDOverlay, - addSeismicLayer, - addWellborePathLayerAndSetReferenceSystem, - makeExtendedTrajectory, -} from "./utils/esvIntersectionUtils"; -import { SeismicFenceData_trans } from "./utils/queryDataTransforms"; - -export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { - const wrapperDivRef = React.useRef(null); - const wrapperDivSize = useElementSize(wrapperDivRef); - const esvIntersectionContainerRef = React.useRef(null); - const esvIntersectionControllerRef = React.useRef(null); - - const [seismicFenceImageBitmap, setSeismicFenceImageBitmap] = React.useState(null); - - const gridLayerUuid = useId(); - const statusWriter = useViewStatusWriter(moduleContext); - - const seismicAddress = moduleContext.useStoreValue("seismicAddress"); - const wellboreAddress = moduleContext.useStoreValue("wellboreAddress"); - - const seismicColorScale = workbenchSettings.useDiscreteColorScale({ - gradientType: ColorScaleGradientType.Diverging, - }); - const seismicColors = seismicColorScale.getColorPalette().getColors(); - - // TMP HARDCODED VALUES - const TEMP_HARDCODED_Z_SCALE = 5; - const TEMP_HARDCODED_EXTENSION = 1000; - - React.useEffect(function initializeEsvIntersectionController() { - if (esvIntersectionContainerRef.current) { - const axisOptions = { xLabel: "x", yLabel: "y", unitOfMeasure: "m" }; - esvIntersectionControllerRef.current = new Controller({ - container: esvIntersectionContainerRef.current, - axisOptions, - }); - - // Initialize/configure controller - addMDOverlay(esvIntersectionControllerRef.current); - esvIntersectionControllerRef.current.addLayer(new GridLayer(gridLayerUuid)); - esvIntersectionControllerRef.current.setBounds([10, 1000], [0, 3000]); - esvIntersectionControllerRef.current.setViewport(1000, 1650, 6000); - esvIntersectionControllerRef.current.zoomPanHandler.zFactor = TEMP_HARDCODED_Z_SCALE; // viewSettings.zScale - } - return () => { - console.debug("controller destroyed"); - esvIntersectionControllerRef.current?.destroy(); - }; - }, []); - - // Get well trajectories - const getWellTrajectoriesQuery = useGetWellTrajectories(wellboreAddress ? [wellboreAddress.uuid] : undefined); - if (getWellTrajectoriesQuery.isError) { - statusWriter.addError("Error loading well trajectories"); - } - - let extendedTrajectory: Trajectory | null = null; - let seismicFencePolyline: SeismicFencePolyline_api | null = null; - if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { - extendedTrajectory = makeExtendedTrajectory(getWellTrajectoriesQuery.data[0], TEMP_HARDCODED_EXTENSION); - - const x_points = extendedTrajectory ? extendedTrajectory.points.map((coord) => coord[0]) : []; - const y_points = extendedTrajectory ? extendedTrajectory.points.map((coord) => coord[1]) : []; - - seismicFencePolyline = { x_points, y_points }; - } - - // Curtain projection on a set of points in 3D - const curtain: number[][] | null = extendedTrajectory - ? IntersectionReferenceSystem.toDisplacement(extendedTrajectory.points, extendedTrajectory.offset) - : null; - - // Get seismic fence data from polyline - const seismicFenceDataQuery = useSeismicFenceDataQuery( - seismicAddress?.caseUuid ?? null, - seismicAddress?.ensemble ?? null, - seismicAddress?.realizationNumber ?? null, - seismicAddress?.attribute ?? null, - seismicAddress?.timeString ?? null, - seismicAddress?.observed ?? null, - seismicFencePolyline, - seismicAddress !== null - ); - if (seismicFenceDataQuery.isError) { - statusWriter.addError("Error loading seismic fence data"); - } - - // Create seismic image using fence data - const seismicImageDataArray: number[][] | null = seismicFenceDataQuery.data - ? createSeismicSliceImageDataArrayFromFenceData(seismicFenceDataQuery.data) - : null; - const seismicImageYAxisValues: number[] | null = seismicFenceDataQuery.data - ? createSeismicSliceImageYAxisValuesArrayFromFenceData(seismicFenceDataQuery.data) - : null; - - React.useEffect( - function generateSeismicFenceImageBitmap() { - const imageDataPoints = seismicImageDataArray ?? []; - const yAxisValues = seismicImageYAxisValues ?? []; - const trajectory = curtain ?? []; - generateSeismicSliceImage( - { datapoints: imageDataPoints, yAxisValues: yAxisValues }, - trajectory, - seismicColors, - { - isLeftToRight: true, - } - ).then((image) => setSeismicFenceImageBitmap(image ?? null)); - }, - [seismicImageDataArray, seismicImageYAxisValues, curtain, seismicColors] - ); - - if ( - esvIntersectionControllerRef.current && - getWellTrajectoriesQuery.data && - getWellTrajectoriesQuery.data.length !== 0 - ) { - esvIntersectionControllerRef.current.removeAllLayers(); - esvIntersectionControllerRef.current.clearAllData(); - - addWellborePathLayerAndSetReferenceSystem( - esvIntersectionControllerRef.current, - getWellTrajectoriesQuery.data[0] - ); - - if (seismicImageDataArray && seismicImageYAxisValues && seismicFenceImageBitmap && curtain) { - addSeismicLayer(esvIntersectionControllerRef.current, { - curtain: curtain, - extension: TEMP_HARDCODED_EXTENSION, - image: seismicFenceImageBitmap, - dataValues: seismicImageDataArray, - yAxisValues: seismicImageYAxisValues, - }); - } - - esvIntersectionControllerRef.current.zoomPanHandler.zFactor = TEMP_HARDCODED_Z_SCALE; - esvIntersectionControllerRef.current.adjustToSize( - Math.max(0, wrapperDivSize.width), - Math.max(0, wrapperDivSize.height - 100) - ); - } - - statusWriter.setLoading(getWellTrajectoriesQuery.isFetching || seismicFenceDataQuery.isFetching); - return ( -
- { - // seismicFenceDataQuery.isError && getWellTrajectoriesQuery.isError ? ( - // Error loading well trajectories and seismic fence data - // ) : seismicFenceDataQuery.isError ? ( - // Error loading seismic fence data - // ) : - getWellTrajectoriesQuery.isError ? ( - Error loading well trajectories - ) : ( -
- ) - } -
- ); -}; - -/** - * Utility function to convert the 1D array of values from the fence data to a 2D array of values - * for the seismic slice image. - * - * For the bit map image, the values are provided s.t. a seismic trace is a column in the image, - * thus the data will be transposed. - * - * trace a,b,c and d - * - * num_traces = 4 - * num_samples = 3 - * fence_traces = [a1, a2, a3, b1, b2, b3, c1, c2, c3, d1, d2, d3] - * - * Image: - * - * a1 b1 c1 d1 - * a2 b2 c2 d2 - * a3 b3 c3 d3 - */ -function createSeismicSliceImageDataArrayFromFenceData(fenceData: SeismicFenceData_trans): number[][] { - const imageArray: number[][] = []; - - const numTraces = fenceData.num_traces; - const numSamples = fenceData.num_trace_samples; - const fenceValues = fenceData.fenceTracesFloat32Arr; - - for (let i = 0; i < numSamples; ++i) { - const row: number[] = []; - for (let j = 0; j < numTraces; ++j) { - const index = i + j * numSamples; - row.push(fenceValues[index]); - } - imageArray.push(row); - } - return imageArray; -} - -/** - * Utility to create an array of values for the Y axis of the seismic slice image. I.e. depth values - * for the seismic depth axis. - */ -function createSeismicSliceImageYAxisValuesArrayFromFenceData(fenceData: SeismicFenceData_trans): number[] { - const yAxisValues: number[] = []; - - const numSamples = fenceData.num_trace_samples; - const minDepth = fenceData.min_fence_depth; - const maxDepth = fenceData.max_fence_depth; - - for (let i = 0; i < numSamples; ++i) { - yAxisValues.push(minDepth + ((maxDepth - minDepth) / numSamples) * i); - } - return yAxisValues; -} diff --git a/frontend/src/modules/registerAllModules.ts b/frontend/src/modules/registerAllModules.ts index 01118989f..4548b56d6 100644 --- a/frontend/src/modules/registerAllModules.ts +++ b/frontend/src/modules/registerAllModules.ts @@ -6,9 +6,9 @@ import "./Grid3D/registerModule"; import "./Grid3DIntersection/registerModule"; import "./Grid3DIntersection/registerModule"; import "./InplaceVolumetrics/registerModule"; +import "./Intersection/registerModule"; import "./Map/registerModule"; import "./Pvt/registerModule"; -import "./SeismicIntersection/registerModule"; import "./SimulationTimeSeries/registerModule"; import "./SimulationTimeSeriesMatrix/registerModule"; import "./SimulationTimeSeriesSensitivity/registerModule"; From 2946b3c7678d89498260e039854f26755d139132 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Fri, 27 Oct 2023 15:08:35 +0200 Subject: [PATCH 20/35] Adjust poetry after merge --- backend/poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index d222c39ce..8107cb7ad 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -3064,4 +3064,4 @@ tests = ["hypothesis", "pytest", "pytest-benchmark", "pytest-mock", "pytest-snap [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "3a5231dade6fe172008bdfab1842eaa86f7b3d71249b06af756fa979948033ee" +content-hash = "2964982b6d91c9ee014d6c788628f7bb5f1812f009002a8462b618c300b9b17e" From 8233b458bd5b25d0b271941079d257a9716cc9a7 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 31 Oct 2023 10:48:50 +0100 Subject: [PATCH 21/35] Fix lint/code style errors/warnings --- .../backend/primary/routers/seismic/router.py | 4 +--- .../primary/routers/seismic/schemas.py | 4 ++-- .../src/services/vds_access/request_types.py | 24 +++++++++---------- .../src/services/vds_access/response_types.py | 22 ++++++++--------- backend/src/services/vds_access/vds_access.py | 3 ++- frontend/src/api/models/SeismicFenceData.ts | 4 ++-- .../src/modules/Intersection/settings.tsx | 2 +- .../utils/esvIntersectionControllerUtils.ts | 5 +++- .../utils/esvIntersectionDataConversion.ts | 14 ++++++++--- frontend/src/modules/Intersection/view.tsx | 8 ++++--- 10 files changed, 51 insertions(+), 39 deletions(-) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 4f38dc232..d51003dc9 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -1,6 +1,4 @@ import logging -import numpy as np -from numpy.typing import NDArray from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query, Body @@ -67,7 +65,7 @@ async def get_seismic_fence( vds_handle: Optional[VdsHandle] = None try: - vds_handle: VdsHandle = await seismic_access.get_vds_handle( + vds_handle = await seismic_access.get_vds_handle( realization=realization_num, seismic_attribute=seismic_attribute, time_or_interval_str=time_or_interval_str, diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index d450efacf..5b36e5328 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -39,7 +39,7 @@ class SeismicFenceData(BaseModel): `Properties:` - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. - - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline, and implies traces + - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline. - `num_trace_samples`: The number of samples in each trace. - `min_fence_depth`: The minimum depth value of the fence. - `max_fence_depth`: The maximum depth value of the fence. @@ -49,7 +49,7 @@ class SeismicFenceData(BaseModel): With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. Fence traces 1D array: [trace_1, trace_2, ..., trace_m] \n - Trace 1D array: [sample_1, sample_2, ..., sample_n] + trace_1, trace_2, ... , trace_m are 1D arrays: [sample_1, sample_2, ..., sample_n] See: - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 diff --git a/backend/src/services/vds_access/request_types.py b/backend/src/services/vds_access/request_types.py index 5ba5becfc..5e852d621 100644 --- a/backend/src/services/vds_access/request_types.py +++ b/backend/src/services/vds_access/request_types.py @@ -2,15 +2,17 @@ from enum import StrEnum from typing import List -""" -This file contains the request types for the vds-slice service found in the following file: - -https://github.com/equinor/vds-slice/blob/master/api/request.go - -Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 - -https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go -""" +###################################################################################################### +# +# This file contains the request types for the vds-slice service found in the following file: +# +# https://github.com/equinor/vds-slice/blob/master/api/request.go +# +# Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 +# +# https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go +# +###################################################################################################### class VdsInterpolation(StrEnum): @@ -66,7 +68,7 @@ def __init__(self, x_points: List[float], y_points: List[float]) -> None: self.x_points = x_points self.y_points = y_points - def to_list(self) -> List[float]: + def to_list(self) -> List[List[float]]: return [[x, y] for x, y in zip(self.x_points, self.y_points)] @@ -94,8 +96,6 @@ class VdsMetadataRequest(VdsRequestedResource): See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/api/request.go#L62-L64 """ - pass - @dataclass class VdsFenceRequest(VdsRequestedResource): diff --git a/backend/src/services/vds_access/response_types.py b/backend/src/services/vds_access/response_types.py index 16a79377d..d550b8371 100644 --- a/backend/src/services/vds_access/response_types.py +++ b/backend/src/services/vds_access/response_types.py @@ -3,15 +3,17 @@ from pydantic import BaseModel -""" -This file contains the response types for the vds-slice service found in the following file: - -https://github.com/equinor/vds-slice/blob/master/internal/core/core.go - -Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 - -https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go -""" +###################################################################################################### +# +# This file contains the response types for the vds-slice service found in the following file: +# +# https://github.com/equinor/vds-slice/blob/master/internal/core/core.go +# +# Master commit hash: ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3 +# +# https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go +# +###################################################################################################### @dataclass @@ -39,8 +41,6 @@ class VdsFenceMetadata(VdsArray): See: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L160-L162 """ - pass - class VdsAxis(BaseModel): """ diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 414db8d7b..352c06612 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -7,6 +7,8 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder, BodyPart import httpx +from src import config + from .response_types import VdsMetadata, VdsFenceMetadata from .request_types import ( VdsCoordinates, @@ -17,7 +19,6 @@ VdsMetadataRequest, ) -from src import config LOGGER = logging.getLogger(__name__) diff --git a/frontend/src/api/models/SeismicFenceData.ts b/frontend/src/api/models/SeismicFenceData.ts index b90f7bf27..76e68e319 100644 --- a/frontend/src/api/models/SeismicFenceData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -12,7 +12,7 @@ import type { B64FloatArray } from './B64FloatArray'; * * `Properties:` * - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. - * - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline, and implies traces + * - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline. * - `num_trace_samples`: The number of samples in each trace. * - `min_fence_depth`: The minimum depth value of the fence. * - `max_fence_depth`: The maximum depth value of the fence. @@ -24,7 +24,7 @@ import type { B64FloatArray } from './B64FloatArray'; * * Fence traces 1D array: [trace_1, trace_2, ..., trace_m] * - * Trace 1D array: [sample_1, sample_2, ..., sample_n] + * trace_1, trace_2, ... , trace_m are 1D arrays: [sample_1, sample_2, ..., sample_n] * * See: * - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 diff --git a/frontend/src/modules/Intersection/settings.tsx b/frontend/src/modules/Intersection/settings.tsx index 9ffbdbb77..d1dcef843 100644 --- a/frontend/src/modules/Intersection/settings.tsx +++ b/frontend/src/modules/Intersection/settings.tsx @@ -18,7 +18,7 @@ import { Select, SelectOption } from "@lib/components/Select"; import { useValidState } from "@lib/hooks/useValidState"; import { useGetWellHeaders } from "@modules/_shared/WellBore"; -import { isEqual, set } from "lodash"; +import { isEqual } from "lodash"; import { useSeismicCubeDirectoryQuery } from "./queryHooks"; import { State } from "./state"; diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts index 8aabf138f..c30136e78 100644 --- a/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts @@ -129,7 +129,10 @@ export function updateLayout( ): void { // Calculate midpoint for xAxis // Need to calculate y... - const _hMid: number = curtain ? (curtain[0][0] + curtain[curtain.length - 1][0]) / 2 - extension : 1000; + + void curtain; // TODO: Remove this line when curtain is used + void extension; // TODO: Remove this line when extension is used + // const _hMid: number = curtain ? (curtain[0][0] + curtain[curtain.length - 1][0]) / 2 - extension : 1000; // this.controller.setViewport(hMid, 1750, 5000); diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts index d3d46843f..f94261690 100644 --- a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts +++ b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts @@ -18,6 +18,9 @@ export function makeExtendedTrajectoryFromWellboreTrajectory( parseFloat(northingArr[idx].toFixed(3)), parseFloat(tvdArr[idx].toFixed(3)), ]); + + // If the first and last coordinates are the same, the trajectory is assumed to be a vertical line. In this case, + // add a coordinate at the start and end of the trajectory to ensure that the trajectory is not considered a vertical line. if (eastingArr[0] == eastingArr[eastingArr.length - 1] && northingArr[0] == northingArr[northingArr.length - 1]) { const addcoordatstart = eastingArr[0] - 100; const addcoordatend = eastingArr[eastingArr.length - 1] + 100; @@ -38,9 +41,14 @@ export function makeExtendedTrajectoryFromWellboreTrajectory( const samplingIncrement = 5; //meters const steps = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrement)); console.debug("Number of samples for intersection ", steps); - const traj = referenceSystem.getExtendedTrajectory(steps, extension, extension); - traj.points = traj.points.map((point) => [parseFloat(point[0].toFixed(3)), parseFloat(point[1].toFixed(3))]); - return traj; + + const extendedTrajectory = referenceSystem.getExtendedTrajectory(steps, extension, extension); + extendedTrajectory.points = extendedTrajectory.points.map((point) => [ + parseFloat(point[0].toFixed(3)), + parseFloat(point[1].toFixed(3)), + ]); + + return extendedTrajectory; } /** diff --git a/frontend/src/modules/Intersection/view.tsx b/frontend/src/modules/Intersection/view.tsx index 3cf217115..d03559b35 100644 --- a/frontend/src/modules/Intersection/view.tsx +++ b/frontend/src/modules/Intersection/view.tsx @@ -133,7 +133,8 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Regenerate seismic fence image when fence data changes // - Must be useEffect due to async generateSeismicSliceImage function - // - seismicFenceDataQuery.data in dependency array: Assumes provides same reference as long as the query data is the same (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) + // - seismicFenceDataQuery.data in dependency array: Assumes provides same reference as long as the query data is the + // same (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) React.useEffect( function generateSeismicFenceImageLayerData() { if (!seismicFenceDataQuery.data) return; @@ -155,7 +156,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) const yAxisValues = newSeismicImageYAxisValues; const trajectory = newWellboreTrajectoryProjection ?? []; - // Note: useQuery has cache, this does not - thereby the image is regenerated when switching back and forth + // Note: No cache, thereby the image is regenerated when switching back and forth generateSeismicSliceImage( { datapoints: imageDataPoints, yAxisValues: yAxisValues }, trajectory, @@ -165,7 +166,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) } ) .then((image) => setSeismicFenceImageBitmapAndStatus({ image: image ?? null, errorStatus: false })) - .catch((_error) => setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: true })); + .catch(() => setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: true })); setWellboreTrajectoryProjection(newWellboreTrajectoryProjection); setSeismicImageDataArray(newSeismicImageDataArray); @@ -179,6 +180,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) [seismicFenceDataQuery.data, extendedWellboreTrajectory] ); + // Update esv-intersection controller when data is ready - keep old data to prevent blank view when fetching new data if (esvIntersectionControllerRef.current && renderWellboreTrajectory) { esvIntersectionControllerRef.current.removeAllLayers(); esvIntersectionControllerRef.current.clearAllData(); From 9312ff2a9aa06242dce73c0671c79adfdaef952c Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 31 Oct 2023 14:58:10 +0100 Subject: [PATCH 22/35] Next iteration --- .../utils/esvIntersectionControllerUtils.ts | 33 +---- .../utils/esvIntersectionDataConversion.ts | 135 ++++++++++++++---- frontend/src/modules/Intersection/view.tsx | 101 +++++++------ 3 files changed, 174 insertions(+), 95 deletions(-) diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts index c30136e78..b0e92d29f 100644 --- a/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts @@ -1,7 +1,6 @@ import { WellBoreTrajectory_api } from "@api"; import { Controller, - IntersectionReferenceSystem, OverlayMouseMoveEvent, SeismicCanvasLayer, WellborepathLayer, @@ -9,12 +8,13 @@ import { getSeismicOptions, } from "@equinor/esv-intersection"; +import { makeReferenceSystemFromWellboreTrajectory } from "./esvIntersectionDataConversion"; + /** * Utility to add md overlay for hover to esv intersection controller */ export function addMDOverlay(controller: Controller) { const elm = controller.overlay.create("md", { - // onMouseMove: (event: any) => { onMouseMove: (event: OverlayMouseMoveEvent) => { const { target, caller, x } = event; const newX = caller.currentStateAsEvent.xScale.invert(x); @@ -26,9 +26,7 @@ export function addMDOverlay(controller: Controller) { target.textContent = Number.isFinite(md) ? `MD: ${md?.toFixed(1)}` : "-"; if (md && (md < 0 || referenceSystem.length < md)) { target.setAttribute("style", "visibility: hidden"); - // target.style.visibility = "hidden"; } else { - // target.style.visibility = "visible"; target.setAttribute("style", "visibility: visible"); } }, @@ -52,35 +50,18 @@ export function addMDOverlay(controller: Controller) { } /** - * Utility to add well bore trajectory and set reference system to esv intersection controller + * Utility to add well bore trajectory to esv intersection controller * - * Sets reference system with trajectory coordinates - overriding any existing reference system + * Sets reference system with trajectory 3D coordinates, controller reference system must be handled outside */ -export function addWellborePathLayerAndSetReferenceSystem( - controller: Controller, - wellBoreTrajectory: WellBoreTrajectory_api -): void { - if ( - wellBoreTrajectory.easting_arr.length !== wellBoreTrajectory.northing_arr.length && - wellBoreTrajectory.northing_arr.length !== wellBoreTrajectory.tvd_msl_arr.length - ) { - throw new Error("Wellbore trajectory coordinate arrays are not of equal length"); - } - - const coords = wellBoreTrajectory.easting_arr.map((easting: number, idx: number) => [ - easting, - wellBoreTrajectory.northing_arr[idx], - wellBoreTrajectory.tvd_msl_arr[idx], - ]); - - controller.setReferenceSystem(new IntersectionReferenceSystem(coords)); - +export function addWellborePathLayer(controller: Controller, wellBoreTrajectory: WellBoreTrajectory_api): void { + const referenceSystem = makeReferenceSystemFromWellboreTrajectory(wellBoreTrajectory); controller.addLayer( new WellborepathLayer("wellborepath", { order: 3, strokeWidth: "4px", stroke: "black", - referenceSystem: controller.referenceSystem, + referenceSystem: referenceSystem, }) ); } diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts index f94261690..e2600a4f5 100644 --- a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts +++ b/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts @@ -8,41 +8,22 @@ import { SeismicFenceData_trans } from "./queryDataTransforms"; */ export function makeExtendedTrajectoryFromWellboreTrajectory( wellboreTrajectory: WellBoreTrajectory_api, - extension: number + extension: number, + samplingIncrementMeters = 5 // // TODO: Number of samples. Needs some thought. ): Trajectory { - const eastingArr = wellboreTrajectory.easting_arr; - const northingArr = wellboreTrajectory.northing_arr; - const tvdArr = wellboreTrajectory.tvd_msl_arr; - const trajectory = eastingArr.map((easting: number, idx: number) => [ - parseFloat(easting.toFixed(3)), - parseFloat(northingArr[idx].toFixed(3)), - parseFloat(tvdArr[idx].toFixed(3)), - ]); + let trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory); - // If the first and last coordinates are the same, the trajectory is assumed to be a vertical line. In this case, - // add a coordinate at the start and end of the trajectory to ensure that the trajectory is not considered a vertical line. - if (eastingArr[0] == eastingArr[eastingArr.length - 1] && northingArr[0] == northingArr[northingArr.length - 1]) { - const addcoordatstart = eastingArr[0] - 100; - const addcoordatend = eastingArr[eastingArr.length - 1] + 100; - const addcoordatstart2 = northingArr[0] - 100; - const addcoordatend2 = northingArr[northingArr.length - 1] + 100; - const firstzcoord = tvdArr[0]; - const lastzcoord = tvdArr[tvdArr.length - 1]; - - trajectory.unshift([addcoordatstart, addcoordatstart2, firstzcoord]); - trajectory.push([addcoordatend, addcoordatend2, lastzcoord]); + if (isVerticalTrajectory(trajectoryXyzPoints)) { + trajectoryXyzPoints = addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints); } - const referenceSystem = new IntersectionReferenceSystem(trajectory); - referenceSystem.offset = trajectory[0][2]; // Offset should be md at start of path + const referenceSystem = new IntersectionReferenceSystem(trajectoryXyzPoints); + referenceSystem.offset = trajectoryXyzPoints[0][2]; // Offset should be md at start of path const displacement = referenceSystem.displacement || 1; - // Number of samples. Needs some thought. - const samplingIncrement = 5; //meters - const steps = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrement)); - console.debug("Number of samples for intersection ", steps); - const extendedTrajectory = referenceSystem.getExtendedTrajectory(steps, extension, extension); + const numPoints = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrementMeters)); + const extendedTrajectory = referenceSystem.getExtendedTrajectory(numPoints, extension, extension); extendedTrajectory.points = extendedTrajectory.points.map((point) => [ parseFloat(point[0].toFixed(3)), parseFloat(point[1].toFixed(3)), @@ -51,6 +32,104 @@ export function makeExtendedTrajectoryFromWellboreTrajectory( return extendedTrajectory; } +/** + * Helper function to check if a trajectory made of 3D coordinates [x,y,z] is a vertical line + * + * Checks for first coordinate with different x and y coordinates than the first point + */ +function isVerticalTrajectory(trajectoryXyzPoints: number[][]): boolean { + if (trajectoryXyzPoints.length === 0) return false; + + const firstPoint = trajectoryXyzPoints[0]; + + if (firstPoint.length !== 3) { + throw new Error("First coordinates of trajectory must be 3D coordinates of length 3"); + } + + // Detect first 3D point which is not on the same x and y coordinates as the first point and return false + for (let i = 1; i < trajectoryXyzPoints.length; ++i) { + const point = trajectoryXyzPoints[i]; + if (point.length !== 3) { + throw new Error("Trajectory points must be 3D coordinates of length 3"); + } + if (point[0] !== firstPoint[0] || point[1] !== firstPoint[1]) { + return false; + } + } + + return true; +} + +/** + * Helper function to add start and end points to trajectory to prevent pure vertical line + * + * This function assumes check of vertical line beforehand, and only performs adding of start and end points + * + * @param trajectoryXyzPoints - Array of 3D coordinates [x,y,z] + */ +function addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints: number[][]): number[][] { + if (trajectoryXyzPoints.length === 0) return []; + + const firstCoordinates = trajectoryXyzPoints[0]; + const lastCoordinates = trajectoryXyzPoints[trajectoryXyzPoints.length - 1]; + + if (firstCoordinates.length !== 3 || lastCoordinates.length !== 3) { + throw new Error("First and last coordinates of trajectory must be 3D coordinates of length 3"); + } + + const modifiedTrajectoryXyzPoints = [...trajectoryXyzPoints]; + + // Compare x (index 0) and y (index 1) coordinates of first and last points + // Add start and end coordinates to trajectory + const addCoordAtStart = firstCoordinates[0] - 100; + const addCoordAtEnd = lastCoordinates[0] + 100; + const addCoordAtStart2 = firstCoordinates[1] - 100; + const addCoordAtEnd2 = lastCoordinates[1] + 100; + const firstZCoord = firstCoordinates[2]; + const lastZCoord = lastCoordinates[2]; + + modifiedTrajectoryXyzPoints.unshift([addCoordAtStart, addCoordAtStart2, firstZCoord]); + modifiedTrajectoryXyzPoints.push([addCoordAtEnd, addCoordAtEnd2, lastZCoord]); + + return modifiedTrajectoryXyzPoints; +} + +/** + * Make an array of 3D coordinates [x,y,z] from a wellbore trajectory + * + * [x,y,z] = [easting, northing, tvd_msl] + */ +export function makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory: WellBoreTrajectory_api): number[][] { + const eastingArr = wellboreTrajectory.easting_arr; + const northingArr = wellboreTrajectory.northing_arr; + const tvdArr = wellboreTrajectory.tvd_msl_arr; + + if (eastingArr.length !== northingArr.length && northingArr.length !== tvdArr.length) { + throw new Error("Wellbore trajectory coordinate arrays are not of same length"); + } + + // Trajectory points: array of 3D coordinates [x,y,z] + const trajectoryXyzPoints = eastingArr.map((easting: number, idx: number) => [ + parseFloat(easting.toFixed(3)), + parseFloat(northingArr[idx].toFixed(3)), + parseFloat(tvdArr[idx].toFixed(3)), + ]); + + return trajectoryXyzPoints; +} + +/** + * Make a reference system from array 3D points [x,y,z] defined by a wellbore trajectory + */ +export function makeReferenceSystemFromWellboreTrajectory( + wellboreTrajectory: WellBoreTrajectory_api +): IntersectionReferenceSystem { + const referenceSystem = new IntersectionReferenceSystem( + makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory) + ); + return referenceSystem; +} + /** * Utility function to convert the 1D array of values from the fence data to a 2D array of values * for the seismic slice image. diff --git a/frontend/src/modules/Intersection/view.tsx b/frontend/src/modules/Intersection/view.tsx index d03559b35..a382a34ae 100644 --- a/frontend/src/modules/Intersection/view.tsx +++ b/frontend/src/modules/Intersection/view.tsx @@ -19,23 +19,31 @@ import { isEqual } from "lodash"; import { useSeismicFenceDataQuery } from "./queryHooks"; import { State } from "./state"; -import { - addMDOverlay, - addSeismicLayer, - addWellborePathLayerAndSetReferenceSystem, -} from "./utils/esvIntersectionControllerUtils"; +import { addMDOverlay, addSeismicLayer, addWellborePathLayer } from "./utils/esvIntersectionControllerUtils"; import { createSeismicSliceImageDataArrayFromFenceData, createSeismicSliceImageYAxisValuesArrayFromFenceData, makeExtendedTrajectoryFromWellboreTrajectory, + makeReferenceSystemFromWellboreTrajectory, } from "./utils/esvIntersectionDataConversion"; +enum SeismicImageBitmapStatus { + ERROR = "error", + INVALID = "invalid", + VALID = "valid", +} + +type SeismicLayerData = { + wellboreTrajectoryXyProjection: number[][]; // Array of 2D projected points [x, y] + seismicImageDataArray: number[][]; // Array of seismic image data values + seismicImageYAxisValues: number[]; // Array of seismic image y axis values +}; + export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); const esvIntersectionContainerRef = React.useRef(null); const esvIntersectionControllerRef = React.useRef(null); - const [extendedWellboreTrajectory, setExtendedWellboreTrajectory] = React.useState(null); const gridLayerUuid = useId(); const statusWriter = useViewStatusWriter(moduleContext); @@ -50,18 +58,19 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) }); const seismicColors = seismicColorScale.getColorPalette().getColors(); + // State for extended wellbore trajectory + const [extendedWellboreTrajectory, setExtendedWellboreTrajectory] = React.useState(null); + // Data for well trajectory layer in esv-intersection (to be in synch with seismic fence layer) const [renderWellboreTrajectory, setRenderWellboreTrajectory] = React.useState(null); // Data for seismic fence layer in esv-intersection const [seismicFencePolyline, setSeismicFencePolyline] = React.useState(null); - const [wellboreTrajectoryProjection, setWellboreTrajectoryProjection] = React.useState(null); - const [seismicImageDataArray, setSeismicImageDataArray] = React.useState(null); - const [seismicImageYAxisValues, setSeismicImageYAxisValues] = React.useState(null); + const [seismicLayerData, setSeismicLayerData] = React.useState(null); const [seismicFenceImageBitmapAndStatus, setSeismicFenceImageBitmapAndStatus] = React.useState<{ image: ImageBitmap | null; - errorStatus: boolean; - }>({ image: null, errorStatus: false }); + status: SeismicImageBitmapStatus; + }>({ image: null, status: SeismicImageBitmapStatus.INVALID }); React.useEffect(function initializeEsvIntersectionController() { if (esvIntersectionContainerRef.current) { @@ -76,7 +85,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) esvIntersectionControllerRef.current.addLayer(new GridLayer(gridLayerUuid)); esvIntersectionControllerRef.current.setBounds([10, 1000], [0, 3000]); esvIntersectionControllerRef.current.setViewport(1000, 1650, 6000); - esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; // viewSettings.zScale + esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; } return () => { console.debug("controller destroyed"); @@ -97,22 +106,27 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) extension ); - const x_points = newExtendedWellboreTrajectory - ? newExtendedWellboreTrajectory.points.map((coord) => coord[0]) - : []; - const y_points = newExtendedWellboreTrajectory - ? newExtendedWellboreTrajectory.points.map((coord) => coord[1]) - : []; + const referenceSystem = makeReferenceSystemFromWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + if (esvIntersectionControllerRef.current) { + esvIntersectionControllerRef.current.setReferenceSystem(referenceSystem); + } if (!isEqual(newExtendedWellboreTrajectory, extendedWellboreTrajectory)) { setExtendedWellboreTrajectory(newExtendedWellboreTrajectory); + + const x_points = newExtendedWellboreTrajectory + ? newExtendedWellboreTrajectory.points.map((coord) => coord[0]) + : []; + const y_points = newExtendedWellboreTrajectory + ? newExtendedWellboreTrajectory.points.map((coord) => coord[1]) + : []; setSeismicFencePolyline({ x_points, y_points }); } // When new well trajectory is loaded, update the renderWellboreTrajectory and clear the seismic fence image if (!isEqual(getWellTrajectoriesQuery.data[0], renderWellboreTrajectory)) { setRenderWellboreTrajectory(getWellTrajectoriesQuery.data[0]); - setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: false }); + setSeismicLayerData(null); } } @@ -133,19 +147,19 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Regenerate seismic fence image when fence data changes // - Must be useEffect due to async generateSeismicSliceImage function - // - seismicFenceDataQuery.data in dependency array: Assumes provides same reference as long as the query data is the + // - seismicFenceDataQuery.data in dependency array: Assumes useQuery provides same reference as long as the query data is the // same (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) React.useEffect( function generateSeismicFenceImageLayerData() { if (!seismicFenceDataQuery.data) return; - // Curtain projection on a set of points in 3D - const newWellboreTrajectoryProjection: number[][] | null = extendedWellboreTrajectory + // Get an array of projected [x, y] points, as 2D curtain projection from a set of trajectory 3D points and offset + const newWellboreTrajectoryXyProjection: number[][] | null = extendedWellboreTrajectory ? IntersectionReferenceSystem.toDisplacement( extendedWellboreTrajectory.points, extendedWellboreTrajectory.offset ) - : null; + : []; const newSeismicImageDataArray = createSeismicSliceImageDataArrayFromFenceData(seismicFenceDataQuery.data); const newSeismicImageYAxisValues = createSeismicSliceImageYAxisValuesArrayFromFenceData( @@ -154,7 +168,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) const imageDataPoints = newSeismicImageDataArray; const yAxisValues = newSeismicImageYAxisValues; - const trajectory = newWellboreTrajectoryProjection ?? []; + const trajectory = newWellboreTrajectoryXyProjection; // Note: No cache, thereby the image is regenerated when switching back and forth generateSeismicSliceImage( @@ -165,19 +179,29 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) isLeftToRight: true, } ) - .then((image) => setSeismicFenceImageBitmapAndStatus({ image: image ?? null, errorStatus: false })) - .catch(() => setSeismicFenceImageBitmapAndStatus({ image: null, errorStatus: true })); - - setWellboreTrajectoryProjection(newWellboreTrajectoryProjection); - setSeismicImageDataArray(newSeismicImageDataArray); - setSeismicImageYAxisValues(newSeismicImageYAxisValues); + .then((image) => + setSeismicFenceImageBitmapAndStatus({ + image: image ?? null, + status: SeismicImageBitmapStatus.VALID, + }) + ) + .catch(() => + setSeismicFenceImageBitmapAndStatus({ image: null, status: SeismicImageBitmapStatus.ERROR }) + ); + + // Update calculated data and wellbore trajectory + setSeismicLayerData({ + wellboreTrajectoryXyProjection: newWellboreTrajectoryXyProjection, + seismicImageDataArray: newSeismicImageDataArray, + seismicImageYAxisValues: newSeismicImageYAxisValues, + }); setRenderWellboreTrajectory( getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0 ? getWellTrajectoriesQuery.data[0] : null ); }, - [seismicFenceDataQuery.data, extendedWellboreTrajectory] + [seismicFenceDataQuery.data, extendedWellboreTrajectory, getWellTrajectoriesQuery.data] ); // Update esv-intersection controller when data is ready - keep old data to prevent blank view when fetching new data @@ -185,20 +209,15 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) esvIntersectionControllerRef.current.removeAllLayers(); esvIntersectionControllerRef.current.clearAllData(); - addWellborePathLayerAndSetReferenceSystem(esvIntersectionControllerRef.current, renderWellboreTrajectory); + addWellborePathLayer(esvIntersectionControllerRef.current, renderWellboreTrajectory); - if ( - seismicImageDataArray && - seismicImageYAxisValues && - seismicFenceImageBitmapAndStatus.image && - wellboreTrajectoryProjection - ) { + if (seismicLayerData && seismicFenceImageBitmapAndStatus.image) { addSeismicLayer(esvIntersectionControllerRef.current, { - curtain: wellboreTrajectoryProjection, + curtain: seismicLayerData.wellboreTrajectoryXyProjection, extension: extension, image: seismicFenceImageBitmapAndStatus.image, - dataValues: seismicImageDataArray, - yAxisValues: seismicImageYAxisValues, + dataValues: seismicLayerData.seismicImageDataArray, + yAxisValues: seismicLayerData.seismicImageYAxisValues, }); } From 213e84f5aecd7b9931f12c013116a4110d5ddee8 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 31 Oct 2023 15:06:35 +0100 Subject: [PATCH 23/35] Change name from Intersection to SeismicIntersection And removed jupyter notebook --- backend/seismic_vds_slice.ipynb | 356 ------------------ .../loadModule.tsx | 2 +- .../queryHooks.tsx | 0 .../registerModule.ts | 8 +- .../settings.tsx | 0 .../state.ts | 0 .../types.ts | 0 .../utils/esvIntersectionControllerUtils.ts | 0 .../utils/esvIntersectionDataConversion.ts | 0 .../utils/queryDataTransforms.ts | 0 .../utils/seismicCubeDirectory.ts | 0 .../view.tsx | 0 frontend/src/modules/registerAllModules.ts | 2 +- 13 files changed, 5 insertions(+), 363 deletions(-) delete mode 100644 backend/seismic_vds_slice.ipynb rename frontend/src/modules/{Intersection => SeismicIntersection}/loadModule.tsx (82%) rename frontend/src/modules/{Intersection => SeismicIntersection}/queryHooks.tsx (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/registerModule.ts (69%) rename frontend/src/modules/{Intersection => SeismicIntersection}/settings.tsx (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/state.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/types.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/utils/esvIntersectionControllerUtils.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/utils/esvIntersectionDataConversion.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/utils/queryDataTransforms.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/utils/seismicCubeDirectory.ts (100%) rename frontend/src/modules/{Intersection => SeismicIntersection}/view.tsx (100%) diff --git a/backend/seismic_vds_slice.ipynb b/backend/seismic_vds_slice.ipynb deleted file mode 100644 index 4b6d04a9c..000000000 --- a/backend/seismic_vds_slice.ipynb +++ /dev/null @@ -1,356 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "axis=[VdsAxis(annotation='Inline', max=217.0, min=0.0, samples=218, unit='unitless'), VdsAxis(annotation='Crossline', max=137.0, min=0.0, samples=138, unit='unitless'), VdsAxis(annotation='Sample', max=1999.0, min=1500.0, samples=500, unit='ms')] boundingBox=VdsBoundingBox(cdp=[[461484.34, 5926563.2], [456083.37, 5936006.48], [462069.5080733945, 5939430.173027524], [467470.4780733945, 5929986.8930275235]], ij=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]], ilxl=[[0.0, 0.0], [217.0, 0.0], [217.0, 137.0], [0.0, 137.0]]) crs=''\n", - "Vds Access coordinate system: \n", - "Flattened fence traces array: [0. 0. 0. ... 0. 0. 0.]\n", - "Number of traces: 10\n", - "Number of samples in trace: 500\n", - "\n", - "\n", - "Number of fence traces: 10\n", - "Length of fence traces: 500\n", - "Shape: Number of traces: 10\n", - "Shape: Number of fence samples: 500\n", - "Fence traces array: [[0. 0. 0. ... 0. 0. 0.]\n", - " [0. 0. 0. ... 0. 0. 0.]\n", - " [0. 0. 0. ... 0. 0. 0.]\n", - " ...\n", - " [0. 0. 0. ... 0. 0. 0.]\n", - " [0. 0. 0. ... 0. 0. 0.]\n", - " [0. 0. 0. ... 0. 0. 0.]]\n", - "First trace: [ 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 -9.2752583e-05 -2.6747005e-04\n", - " -3.7114468e-04 -6.2928506e-04 -1.0021555e-03 -1.5351177e-03\n", - " -2.3295472e-03 -3.4679242e-03 -5.1008533e-03 -7.0881671e-03\n", - " -9.8076593e-03 -1.3284918e-02 -1.7565463e-02 -2.2672666e-02\n", - " -2.8664507e-02 -3.5130404e-02 -4.1960362e-02 -4.8559703e-02\n", - " -5.4532271e-02 -5.9226822e-02 -6.1765946e-02 -6.1519943e-02\n", - " -5.7856992e-02 -5.0333757e-02 -3.8805325e-02 -2.3421533e-02\n", - " -5.0506815e-03 1.5544317e-02 3.6964323e-02 5.7683878e-02\n", - " 7.6133333e-02 9.0877421e-02 1.0078802e-01 1.0518546e-01\n", - " 1.0392365e-01 9.7404234e-02 8.6516015e-02 7.2507888e-02\n", - " 5.6816332e-02 4.0875100e-02 2.5938479e-02 1.2945393e-02\n", - " 2.4442780e-03 -5.4136692e-03 -1.0817782e-02 -1.4193936e-02\n", - " -1.6076926e-02 -1.6985366e-02 -1.7322414e-02 -1.7318489e-02\n", - " -1.7022343e-02 -1.6336577e-02 -1.5085100e-02 -1.3094651e-02\n", - " -1.0271180e-02 -6.6546444e-03 -2.4414081e-03 2.0289721e-03\n", - " 6.3185203e-03 9.9569801e-03 1.2515657e-02 1.3674809e-02\n", - " 1.3272504e-02 1.1327312e-02 8.0323834e-03 3.7238598e-03\n", - " -1.1693260e-03 -6.0602892e-03 -1.0829658e-02 -1.4861319e-02\n", - " -1.7981935e-02 -1.9993670e-02 -2.0909099e-02 -2.0820366e-02\n", - " -1.9884996e-02 -1.8224414e-02 -1.6366670e-02 -1.4108810e-02\n", - " -1.1817116e-02 -9.6391533e-03 -7.6238750e-03 -5.8751642e-03\n", - " -4.5101382e-03 -3.2928942e-03 -2.4548089e-03 -1.7266531e-03\n", - " -1.1366470e-03 -8.2339306e-04 -5.0562772e-04 -3.1820493e-04\n", - " -2.1667115e-04 -1.4869146e-04 -1.4121966e-04 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00\n", - " 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00]\n" - ] - } - ], - "source": [ - "# Set env variable before import of vds_access!\n", - "import os\n", - "os.environ[\"WEBVIZ_VDS_HOST_ADDRESS\"] = \"https://server-oneseismictest-dev.playground.radix.equinor.com\"\n", - "\n", - "from sumo.wrapper import SumoClient\n", - "\n", - "\n", - "# from fmu.sumo.explorer import Explorer\n", - "# from fmu.sumo.explorer.objects.cube_collection import CubeCollection\n", - "from fmu.sumo.explorer.objects import CaseCollection\n", - "\n", - "from services.vds_access.vds_access import VdsAccess\n", - "# from services.vds_access. import VdsMetadata\n", - "from services.sumo_access.seismic_access import SeismicAccess\n", - "from services.vds_access.request_types import VdsCoordinates\n", - "from services.vds_access.response_types import VdsMetadata\n", - "\n", - "from src.services.sumo_access._helpers import SumoCase\n", - "\n", - "\n", - "import numpy as np\n", - "\n", - "\n", - "\n", - "# Test data\n", - "case_uuid = \"c619f32d-3ada-4e5e-8d3c-330f940e88f8\"\n", - "realization = 0\n", - "iteration = \"iter-0\"\n", - "cube_tagname = \"amplitude_far_time\"\n", - "timestep = None\n", - "timestamp = \"2018-01-01T00:00:00\"\n", - "observed = False\n", - "\n", - "sumo_client = SumoClient(env=\"prod\")\n", - "case_collection = CaseCollection(sumo_client).filter(uuid=case_uuid) # Existing working case\n", - "case = case_collection[0]\n", - "\n", - "sumo_case = await SumoCase.from_case_uuid(sumo_client.authenticate(), case_uuid=case_uuid)\n", - "\n", - "# Setup\n", - "seismic_access = SeismicAccess(sumo_client=sumo_client, case=case, case_uuid=case_uuid, iteration_name=iteration)\n", - "vds_handle = await seismic_access.get_vds_handle(seismic_attribute=cube_tagname,realization=realization, time_or_interval_str=timestamp)\n", - "vds_access = VdsAccess(sas_token=vds_handle.sas_token, vds_url=vds_handle.vds_url)\n", - "\n", - "num_polyline_points = 10\n", - "x_points = np.linspace(462799.467, 464923.32, num_polyline_points)\n", - "y_points = np.linspace(5929653.056, 5933656.999, num_polyline_points)\n", - "\n", - "[\n", - " flattened_fence_traces_array,\n", - " num_traces,\n", - " num_trace_samples,\n", - "] = await vds_access.get_flattened_fence_traces_array_and_metadata(coordinates=VdsCoordinates(x_points=x_points, y_points=y_points))\n", - "[\n", - " fence_traces_array,\n", - " _num_traces,\n", - " _num_trace_samples,\n", - "] = await vds_access.get_fence_traces_array_and_metadata(coordinates=VdsCoordinates(x_points=x_points, y_points=y_points))\n", - "\n", - "\n", - "meta: VdsMetadata = await vds_access.get_metadata()\n", - "if len(meta.axis) != 3:\n", - " raise ValueError(f\"Expected 3 axes, got {len(meta.axis)}\")\n", - "depth_axis_meta = meta.axis[2]\n", - "print(meta)\n", - "print(f'Vds Access coordinate system: {meta.crs}')\n", - "\n", - "# coordinate_system = await sumo_case.get_coordinate_system_identifier()\n", - "# print(f'Sumo case coordinate system: {coordinate_system}')\n", - "\n", - "print(f'Flattened fence traces array: {flattened_fence_traces_array}')\n", - "print(f'Number of traces: {num_traces}')\n", - "print(f'Number of samples in trace: {num_trace_samples}')\n", - "\n", - "print(\"\\n\")\n", - "print(f'Number of fence traces: {len(fence_traces_array)}')\n", - "print(f'Length of fence traces: {len(fence_traces_array[0])}')\n", - "print(f'Shape: Number of traces: {fence_traces_array.shape[0]}')\n", - "print(f'Shape: Number of fence samples: {fence_traces_array.shape[1]}')\n", - "print(f'Fence traces array: {fence_traces_array}')\n", - "print(f'First trace: {fence_traces_array[0]}')\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "## TEST ROUTER\n", - "\n", - "sumo_client = SumoClient(env=\"prod\")\n", - "\n", - "from src.backend.primary.routers.seismic.router import get_fence\n", - "from src.backend.primary.routers.seismic.schemas import SeismicFencePolyline\n", - "from src.backend.auth.auth_helper import AuthHelper, AuthenticatedUser\n", - "\n", - "sumo_client = SumoClient(env=\"prod\")\n", - "\n", - "case_uuid = \"c619f32d-3ada-4e5e-8d3c-330f940e88f8\"\n", - "realization = 0\n", - "iteration = \"iter-0\"\n", - "cube_tagname = \"amplitude_far_time\"\n", - "timestep = None\n", - "timestamp = \"2018-01-01T00:00:00\"\n", - "observed = False\n", - "\n", - "x_points = [463156.911, 463564.402, 463637.925, 463690.658, 463910.452]\n", - "y_points = [5929542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122]\n", - "\n", - "x_points = [463156.911, 463564.402, 463637.925, 463690.658]\n", - "y_points = [5929542.294, 5931057.803, 5931184.235, 5931278.837]\n", - "\n", - "#authenticated_user = AuthHelper.get_authenticated_user()\n", - "\n", - "#seismic_fence = await get_seismic_fence(authenticated_user, case_uuid, iteration, realization, cube_tagname, timestamp, observed, SeismicFencePolyline(x_points=x_points, y_points=y_points))" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]\n", - "[[ 1 2 3]\n", - " [ 4 5 6]\n", - " [ 7 8 9]\n", - " [10 11 12]]\n", - "[[ 1 2 3 4 5 6 7 8 9 10 11 12]]\n", - "[ 1 2 3 4 5 6 7 8 9 10 11 12]\n", - "[ 1 4 7 10 2 5 8 11 3 6 9 12]\n", - "[['t11', 't12', 't13'], ['t21', 't22', 't23'], ['t31', 't32', 't33']]\n", - "['t11' 't12' 't13' 't21' 't22' 't23' 't31' 't32' 't33']\n", - "['t11' 't21' 't31' 't12' 't22' 't32' 't13' 't23' 't33']\n" - ] - } - ], - "source": [ - "import numpy as np\n", - "from typing import List\n", - "\n", - "test_array = [[1,2,3],[4,5,6],[7,8,9],[10, 11, 12]]\n", - "test_bytes = np.array(test_array).tobytes()\n", - "\n", - "test_array_np_3x3 = np.ndarray(buffer=test_bytes, dtype=np.int64, shape=(4,3))\n", - "test_array_np_1x9 = np.ndarray(buffer=test_bytes, dtype=np.int64, shape=(1,12), order=\"C\")\n", - "\n", - "test_flattened_C = test_array_np_3x3.flatten(order=\"C\")\n", - "test_flattened_F = test_array_np_3x3.flatten(order=\"F\")\n", - "print(test_array)\n", - "print(test_array_np_3x3)\n", - "print(test_array_np_1x9)\n", - "print(test_flattened_C)\n", - "print(test_flattened_F)\n", - "\n", - "# Test code:\n", - "test_array = [[\"t11\",\"t12\",\"t13\"], [\"t21\",\"t22\",\"t23\"], [\"t31\",\"t32\",\"t33\"]]\n", - "test_flattened_array_C = np.asarray(test_array).ravel(order='C')\n", - "test_flattened_array_F = np.asarray(test_array).ravel(order='F')\n", - "print(test_array)\n", - "print(test_flattened_array_C)\n", - "print(test_flattened_array_F)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "backend-ODIrNPVE-py3.11", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/frontend/src/modules/Intersection/loadModule.tsx b/frontend/src/modules/SeismicIntersection/loadModule.tsx similarity index 82% rename from frontend/src/modules/Intersection/loadModule.tsx rename to frontend/src/modules/SeismicIntersection/loadModule.tsx index 86d21be23..b1c03e691 100644 --- a/frontend/src/modules/Intersection/loadModule.tsx +++ b/frontend/src/modules/SeismicIntersection/loadModule.tsx @@ -11,7 +11,7 @@ const defaultState: State = { zScale: 5, }; -const module = ModuleRegistry.initModule("Intersection", defaultState); +const module = ModuleRegistry.initModule("SeismicIntersection", defaultState); module.viewFC = view; module.settingsFC = settings; diff --git a/frontend/src/modules/Intersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx similarity index 100% rename from frontend/src/modules/Intersection/queryHooks.tsx rename to frontend/src/modules/SeismicIntersection/queryHooks.tsx diff --git a/frontend/src/modules/Intersection/registerModule.ts b/frontend/src/modules/SeismicIntersection/registerModule.ts similarity index 69% rename from frontend/src/modules/Intersection/registerModule.ts rename to frontend/src/modules/SeismicIntersection/registerModule.ts index f519bdbae..838cb71ef 100644 --- a/frontend/src/modules/Intersection/registerModule.ts +++ b/frontend/src/modules/SeismicIntersection/registerModule.ts @@ -1,13 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; -// import { preview } from "./preview"; import { State } from "./state"; -// TODO: Add preview? ModuleRegistry.registerModule({ - moduleName: "Intersection", - defaultTitle: "Intersection", + moduleName: "SeismicIntersection", + defaultTitle: "Seismic Intersection", syncableSettingKeys: [SyncSettingKey.ENSEMBLE], - description: "Visualization of intersection data with a wellbore", + description: "Visualization of intersection data with a wellbore and seismic fence", }); diff --git a/frontend/src/modules/Intersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx similarity index 100% rename from frontend/src/modules/Intersection/settings.tsx rename to frontend/src/modules/SeismicIntersection/settings.tsx diff --git a/frontend/src/modules/Intersection/state.ts b/frontend/src/modules/SeismicIntersection/state.ts similarity index 100% rename from frontend/src/modules/Intersection/state.ts rename to frontend/src/modules/SeismicIntersection/state.ts diff --git a/frontend/src/modules/Intersection/types.ts b/frontend/src/modules/SeismicIntersection/types.ts similarity index 100% rename from frontend/src/modules/Intersection/types.ts rename to frontend/src/modules/SeismicIntersection/types.ts diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts similarity index 100% rename from frontend/src/modules/Intersection/utils/esvIntersectionControllerUtils.ts rename to frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts diff --git a/frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts similarity index 100% rename from frontend/src/modules/Intersection/utils/esvIntersectionDataConversion.ts rename to frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts diff --git a/frontend/src/modules/Intersection/utils/queryDataTransforms.ts b/frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts similarity index 100% rename from frontend/src/modules/Intersection/utils/queryDataTransforms.ts rename to frontend/src/modules/SeismicIntersection/utils/queryDataTransforms.ts diff --git a/frontend/src/modules/Intersection/utils/seismicCubeDirectory.ts b/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts similarity index 100% rename from frontend/src/modules/Intersection/utils/seismicCubeDirectory.ts rename to frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts diff --git a/frontend/src/modules/Intersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx similarity index 100% rename from frontend/src/modules/Intersection/view.tsx rename to frontend/src/modules/SeismicIntersection/view.tsx diff --git a/frontend/src/modules/registerAllModules.ts b/frontend/src/modules/registerAllModules.ts index 4548b56d6..01118989f 100644 --- a/frontend/src/modules/registerAllModules.ts +++ b/frontend/src/modules/registerAllModules.ts @@ -6,9 +6,9 @@ import "./Grid3D/registerModule"; import "./Grid3DIntersection/registerModule"; import "./Grid3DIntersection/registerModule"; import "./InplaceVolumetrics/registerModule"; -import "./Intersection/registerModule"; import "./Map/registerModule"; import "./Pvt/registerModule"; +import "./SeismicIntersection/registerModule"; import "./SimulationTimeSeries/registerModule"; import "./SimulationTimeSeriesMatrix/registerModule"; import "./SimulationTimeSeriesSensitivity/registerModule"; From e7c99fab54718af4c017903058cc4af69b065337 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 31 Oct 2023 15:42:39 +0100 Subject: [PATCH 24/35] Revert unused sumo access code --- backend/src/services/sumo_access/_helpers.py | 6 +----- backend/src/services/sumo_access/queries/case.py | 13 ------------- .../utils/esvIntersectionControllerUtils.ts | 1 - 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/backend/src/services/sumo_access/_helpers.py b/backend/src/services/sumo_access/_helpers.py index 4357f3d6b..d5d06fc4e 100644 --- a/backend/src/services/sumo_access/_helpers.py +++ b/backend/src/services/sumo_access/_helpers.py @@ -4,7 +4,7 @@ from fmu.sumo.explorer.objects import CaseCollection, Case from src import config -from .queries.case import get_stratigraphic_column_identifier, get_field_identifiers, get_coordinate_system_identifier +from .queries.case import get_stratigraphic_column_identifier, get_field_identifiers def create_sumo_client_instance(access_token: str) -> SumoClient: @@ -47,10 +47,6 @@ async def get_field_identifiers(self) -> List[str]: """Retrieve the field identifiers for a case""" return await get_field_identifiers(self._sumo_client, self._case_uuid) - async def get_coordinate_system_identifier(self) -> str: - """Retrieve the coordinate system identifier for a case""" - return await get_coordinate_system_identifier(self._sumo_client, self._case_uuid) - class SumoEnsemble(SumoCase): def __init__(self, sumo_client: SumoClient, case: Case, case_uuid: str, iteration_name: str): diff --git a/backend/src/services/sumo_access/queries/case.py b/backend/src/services/sumo_access/queries/case.py index d334a47d8..6c5ee1455 100644 --- a/backend/src/services/sumo_access/queries/case.py +++ b/backend/src/services/sumo_access/queries/case.py @@ -28,16 +28,3 @@ async def get_field_identifiers(sumo_client: SumoClient, case_id: str) -> List[s hits = response["hits"]["hits"] fields = hits[0]["_source"]["masterdata"]["smda"]["field"] return [field["identifier"] for field in fields] - - -async def get_coordinate_system_identifier(sumo_client: SumoClient, case_id: str) -> str: - """Get coordunate syte identifier for a case (assuming unique for all objects)""" - response = await sumo_client.get_async( - "/search", - query=f"_sumo.parent_object:{case_id}", - size=1, - select="masterdata.smda.coordinate_system.identifier", - ) - - hits = response["hits"]["hits"] - return hits[0]["_source"]["masterdata"]["smda"]["coordinate_system"]["identifier"] diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts index b0e92d29f..1b33fea83 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts @@ -91,7 +91,6 @@ export function addSeismicLayer( }); layer.data = { image: image, options: getSeismicOptions(info) }; controller.addLayer(layer); - // addSeismicOverlay(controller, dataValues); } export type SeismicUpdateLayoutOptions = { From 448671153e1e8fac6ff797c80b4b85a00e57c54b Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Tue, 31 Oct 2023 15:57:58 +0100 Subject: [PATCH 25/35] Adjust bitmap and status usage --- frontend/src/modules/SeismicIntersection/view.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index a382a34ae..c6f75e4ad 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -179,12 +179,19 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) isLeftToRight: true, } ) - .then((image) => + .then((image) => { + if (!image) { + setSeismicFenceImageBitmapAndStatus({ + image: null, + status: SeismicImageBitmapStatus.INVALID, + }); + return; + } setSeismicFenceImageBitmapAndStatus({ - image: image ?? null, + image: image, status: SeismicImageBitmapStatus.VALID, - }) - ) + }); + }) .catch(() => setSeismicFenceImageBitmapAndStatus({ image: null, status: SeismicImageBitmapStatus.ERROR }) ); From d2c51874ce21887e56321ed3f2fc6c303b29f473 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 2 Nov 2023 12:45:05 +0100 Subject: [PATCH 26/35] Adjust data conversion methods arguments and remove unused function --- .../utils/esvIntersectionControllerUtils.ts | 34 +--------- .../utils/esvIntersectionDataConversion.ts | 27 ++++---- .../src/modules/SeismicIntersection/view.tsx | 63 +++++++++++-------- 3 files changed, 52 insertions(+), 72 deletions(-) diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts index 1b33fea83..38251bbe5 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts @@ -1,4 +1,3 @@ -import { WellBoreTrajectory_api } from "@api"; import { Controller, OverlayMouseMoveEvent, @@ -8,7 +7,7 @@ import { getSeismicOptions, } from "@equinor/esv-intersection"; -import { makeReferenceSystemFromWellboreTrajectory } from "./esvIntersectionDataConversion"; +import { makeReferenceSystemFromTrajectoryXyzPoints } from "./esvIntersectionDataConversion"; /** * Utility to add md overlay for hover to esv intersection controller @@ -54,8 +53,8 @@ export function addMDOverlay(controller: Controller) { * * Sets reference system with trajectory 3D coordinates, controller reference system must be handled outside */ -export function addWellborePathLayer(controller: Controller, wellBoreTrajectory: WellBoreTrajectory_api): void { - const referenceSystem = makeReferenceSystemFromWellboreTrajectory(wellBoreTrajectory); +export function addWellborePathLayer(controller: Controller, wellboreTrajectoryXyzPoints: number[][]): void { + const referenceSystem = makeReferenceSystemFromTrajectoryXyzPoints(wellboreTrajectoryXyzPoints); controller.addLayer( new WellborepathLayer("wellborepath", { order: 3, @@ -92,30 +91,3 @@ export function addSeismicLayer( layer.data = { image: image, options: getSeismicOptions(info) }; controller.addLayer(layer); } - -export type SeismicUpdateLayoutOptions = { - width: number; - height: number; - zScale: number; - curtain: number[][] | null; - extension: number; -}; -/** - * Utility to update layout of esv intersection controller - */ -export function updateLayout( - controller: Controller, - { width, height, zScale, curtain, extension }: SeismicUpdateLayoutOptions -): void { - // Calculate midpoint for xAxis - // Need to calculate y... - - void curtain; // TODO: Remove this line when curtain is used - void extension; // TODO: Remove this line when extension is used - // const _hMid: number = curtain ? (curtain[0][0] + curtain[curtain.length - 1][0]) / 2 - extension : 1000; - - // this.controller.setViewport(hMid, 1750, 5000); - - controller.adjustToSize(width, height); - controller.zoomPanHandler.zFactor = zScale; -} diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts index e2600a4f5..ffefec189 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts @@ -4,15 +4,15 @@ import { IntersectionReferenceSystem, Trajectory } from "@equinor/esv-intersecti import { SeismicFenceData_trans } from "./queryDataTransforms"; /** - * Utility to make extended trajectory object from wellbore trajectory and extension + * Utility to make extended trajectory object from array of 3D trajectory coordinates [x,y,z] and extension + * + * TODO: Number of samples. Needs some thought for future */ -export function makeExtendedTrajectoryFromWellboreTrajectory( - wellboreTrajectory: WellBoreTrajectory_api, +export function makeExtendedTrajectoryFromTrajectoryXyzPoints( + trajectoryXyzPoints: number[][], extension: number, - samplingIncrementMeters = 5 // // TODO: Number of samples. Needs some thought. + samplingIncrementMeters = 5 ): Trajectory { - let trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory); - if (isVerticalTrajectory(trajectoryXyzPoints)) { trajectoryXyzPoints = addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints); } @@ -61,7 +61,7 @@ function isVerticalTrajectory(trajectoryXyzPoints: number[][]): boolean { } /** - * Helper function to add start and end points to trajectory to prevent pure vertical line + * Helper function to add start and end points to array of 3D trajectory coordinates [x,y,z] to prevent pure vertical line * * This function assumes check of vertical line beforehand, and only performs adding of start and end points * @@ -97,7 +97,8 @@ function addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints: nu /** * Make an array of 3D coordinates [x,y,z] from a wellbore trajectory * - * [x,y,z] = [easting, northing, tvd_msl] + * @param wellboreTrajectory - Wellbore trajectory object + * @returns Array of 3D coordinates [x,y,z] - with [x,y,z] = [easting, northing, tvd_msl] */ export function makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory: WellBoreTrajectory_api): number[][] { const eastingArr = wellboreTrajectory.easting_arr; @@ -119,14 +120,12 @@ export function makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory } /** - * Make a reference system from array 3D points [x,y,z] defined by a wellbore trajectory + * Make a reference system from array of 3D coordinates [x,y,z] defined for a trajectory */ -export function makeReferenceSystemFromWellboreTrajectory( - wellboreTrajectory: WellBoreTrajectory_api +export function makeReferenceSystemFromTrajectoryXyzPoints( + trajectoryXyzPoints: number[][] ): IntersectionReferenceSystem { - const referenceSystem = new IntersectionReferenceSystem( - makeTrajectoryXyzPointsFromWellboreTrajectory(wellboreTrajectory) - ); + const referenceSystem = new IntersectionReferenceSystem(trajectoryXyzPoints); return referenceSystem; } diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index c6f75e4ad..63d31cdf6 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -23,8 +23,9 @@ import { addMDOverlay, addSeismicLayer, addWellborePathLayer } from "./utils/esv import { createSeismicSliceImageDataArrayFromFenceData, createSeismicSliceImageYAxisValuesArrayFromFenceData, - makeExtendedTrajectoryFromWellboreTrajectory, - makeReferenceSystemFromWellboreTrajectory, + makeExtendedTrajectoryFromTrajectoryXyzPoints, + makeReferenceSystemFromTrajectoryXyzPoints, + makeTrajectoryXyzPointsFromWellboreTrajectory, } from "./utils/esvIntersectionDataConversion"; enum SeismicImageBitmapStatus { @@ -34,7 +35,7 @@ enum SeismicImageBitmapStatus { } type SeismicLayerData = { - wellboreTrajectoryXyProjection: number[][]; // Array of 2D projected points [x, y] + trajectoryXyProjection: number[][]; // Array of 2D projected points [x, y] seismicImageDataArray: number[][]; // Array of seismic image data values seismicImageYAxisValues: number[]; // Array of seismic image y axis values }; @@ -58,11 +59,13 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) }); const seismicColors = seismicColorScale.getColorPalette().getColors(); - // State for extended wellbore trajectory + // Extended wellbore trajectory for creating intersection/fence extended on both sides of wellbore const [extendedWellboreTrajectory, setExtendedWellboreTrajectory] = React.useState(null); - // Data for well trajectory layer in esv-intersection (to be in synch with seismic fence layer) - const [renderWellboreTrajectory, setRenderWellboreTrajectory] = React.useState(null); + // Array of 3D points [x,y,z] for well trajectory layer in esv-intersection (to be in synch with seismic fence layer) + const [renderWellboreTrajectoryXyzPoints, setRenderWellboreTrajectoryXyzPoints] = React.useState( + null + ); // Data for seismic fence layer in esv-intersection const [seismicFencePolyline, setSeismicFencePolyline] = React.useState(null); @@ -101,12 +104,13 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Use first trajectory and create polyline for seismic fence query, and extended wellbore trajectory for generating seismic fence image if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { - const newExtendedWellboreTrajectory = makeExtendedTrajectoryFromWellboreTrajectory( - getWellTrajectoriesQuery.data[0], + const trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + const newExtendedWellboreTrajectory = makeExtendedTrajectoryFromTrajectoryXyzPoints( + trajectoryXyzPoints, extension ); - const referenceSystem = makeReferenceSystemFromWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + const referenceSystem = makeReferenceSystemFromTrajectoryXyzPoints(trajectoryXyzPoints); if (esvIntersectionControllerRef.current) { esvIntersectionControllerRef.current.setReferenceSystem(referenceSystem); } @@ -123,9 +127,9 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) setSeismicFencePolyline({ x_points, y_points }); } - // When new well trajectory is loaded, update the renderWellboreTrajectory and clear the seismic fence image - if (!isEqual(getWellTrajectoriesQuery.data[0], renderWellboreTrajectory)) { - setRenderWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + // When new well trajectory 3D points are loaded, update the render trajectory and clear the seismic fence image + if (!isEqual(trajectoryXyzPoints, renderWellboreTrajectoryXyzPoints)) { + setRenderWellboreTrajectoryXyzPoints(trajectoryXyzPoints); setSeismicLayerData(null); } } @@ -147,14 +151,14 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Regenerate seismic fence image when fence data changes // - Must be useEffect due to async generateSeismicSliceImage function - // - seismicFenceDataQuery.data in dependency array: Assumes useQuery provides same reference as long as the query data is the - // same (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) + // - seismicFenceDataQuery.data in dependency array: Assumes useQuery provides same reference as long as the query data is the same + // (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) React.useEffect( function generateSeismicFenceImageLayerData() { if (!seismicFenceDataQuery.data) return; - // Get an array of projected [x, y] points, as 2D curtain projection from a set of trajectory 3D points and offset - const newWellboreTrajectoryXyProjection: number[][] | null = extendedWellboreTrajectory + // Get an array of projected 2D points [x, y], as 2D curtain projection from a set of trajectory 3D points and offset + const newExtendedWellboreTrajectoryXyProjection: number[][] | null = extendedWellboreTrajectory ? IntersectionReferenceSystem.toDisplacement( extendedWellboreTrajectory.points, extendedWellboreTrajectory.offset @@ -168,7 +172,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) const imageDataPoints = newSeismicImageDataArray; const yAxisValues = newSeismicImageYAxisValues; - const trajectory = newWellboreTrajectoryXyProjection; + const trajectory = newExtendedWellboreTrajectoryXyProjection; // Note: No cache, thereby the image is regenerated when switching back and forth generateSeismicSliceImage( @@ -196,31 +200,35 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) setSeismicFenceImageBitmapAndStatus({ image: null, status: SeismicImageBitmapStatus.ERROR }) ); - // Update calculated data and wellbore trajectory + // Update calculated seismic data setSeismicLayerData({ - wellboreTrajectoryXyProjection: newWellboreTrajectoryXyProjection, + trajectoryXyProjection: newExtendedWellboreTrajectoryXyProjection, seismicImageDataArray: newSeismicImageDataArray, seismicImageYAxisValues: newSeismicImageYAxisValues, }); - setRenderWellboreTrajectory( - getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0 - ? getWellTrajectoriesQuery.data[0] - : null - ); + + // Update wellbore trajectory + let newRenderWellboreTrajectoryXyzPoints: number[][] | null = null; + if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { + newRenderWellboreTrajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory( + getWellTrajectoriesQuery.data[0] + ); + } + setRenderWellboreTrajectoryXyzPoints(newRenderWellboreTrajectoryXyzPoints); }, [seismicFenceDataQuery.data, extendedWellboreTrajectory, getWellTrajectoriesQuery.data] ); // Update esv-intersection controller when data is ready - keep old data to prevent blank view when fetching new data - if (esvIntersectionControllerRef.current && renderWellboreTrajectory) { + if (esvIntersectionControllerRef.current && renderWellboreTrajectoryXyzPoints) { esvIntersectionControllerRef.current.removeAllLayers(); esvIntersectionControllerRef.current.clearAllData(); - addWellborePathLayer(esvIntersectionControllerRef.current, renderWellboreTrajectory); + addWellborePathLayer(esvIntersectionControllerRef.current, renderWellboreTrajectoryXyzPoints); if (seismicLayerData && seismicFenceImageBitmapAndStatus.image) { addSeismicLayer(esvIntersectionControllerRef.current, { - curtain: seismicLayerData.wellboreTrajectoryXyProjection, + curtain: seismicLayerData.trajectoryXyProjection, extension: extension, image: seismicFenceImageBitmapAndStatus.image, dataValues: seismicLayerData.seismicImageDataArray, @@ -228,6 +236,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) }); } + // Update layout esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; esvIntersectionControllerRef.current.adjustToSize( Math.max(0, wrapperDivSize.width), From b0f533237be5481cabd9f75f2e651e7678d58cf3 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 2 Nov 2023 13:03:51 +0100 Subject: [PATCH 27/35] Adjustments --- .../modules/SeismicIntersection/settings.tsx | 36 +++++++++++++------ .../src/modules/SeismicIntersection/view.tsx | 2 +- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx index d1dcef843..8614c7bda 100644 --- a/frontend/src/modules/SeismicIntersection/settings.tsx +++ b/frontend/src/modules/SeismicIntersection/settings.tsx @@ -160,8 +160,14 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: function handleRealizationTextChanged(event: React.ChangeEvent) { const base10 = 10; const realNum = parseInt(event.target.value, base10); - if (realNum >= 0) { + const isValidRealNum = selectedEnsembleIdent + ? ensembleSet.findEnsemble(selectedEnsembleIdent)?.getRealizations().includes(realNum) + : null; + if (realNum >= 0 && isValidRealNum) { setRealizationNumber(realNum); + return; + } else { + setRealizationNumber(0); } } @@ -214,16 +220,24 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: return (
- - +
+ + +
Date: Thu, 2 Nov 2023 14:36:59 +0100 Subject: [PATCH 28/35] Add dummy vds host address for CI --- .github/workflows/webviz.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/webviz.yml b/.github/workflows/webviz.yml index 5a43af313..046c7d19d 100644 --- a/.github/workflows/webviz.yml +++ b/.github/workflows/webviz.yml @@ -54,7 +54,7 @@ jobs: - name: 🕵️ Check auto-generated frontend code is in sync with backend run: | docker build -f backend.Dockerfile -t backend:latest . - CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env UVICORN_ENTRYPOINT=src.backend.primary.main:app --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 backend:latest) + CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env UVICORN_ENTRYPOINT=src.backend.primary.main:app --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 --env WEBVIZ_VDS_HOST_ADDRESS=0 backend:latest) sleep 5 # Ensure the backend server is up and running exposing /openapi.json npm run generate-api --prefix ./frontend docker stop $CONTAINER_ID @@ -96,6 +96,7 @@ jobs: WEBVIZ_CLIENT_SECRET: 0 WEBVIZ_SMDA_SUBSCRIPTION_KEY: 0 WEBVIZ_SMDA_RESOURCE_SCOPE: 0 + WEBVIZ_VDS_HOST_ADDRESS: 0 run: | pytest ./tests/unit From 9e27bbe878f901c205646c82d63acba8c43d0c34 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 2 Nov 2023 14:52:22 +0100 Subject: [PATCH 29/35] Adjust documentation --- backend/src/backend/primary/routers/seismic/router.py | 2 +- backend/src/backend/primary/routers/seismic/schemas.py | 2 +- frontend/src/api/models/SeismicFenceData.ts | 2 +- frontend/src/api/services/SeismicService.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index d51003dc9..162fb470d 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -50,7 +50,7 @@ async def get_seismic_fence( """Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. - Each trace has number of samples equal length, and is a set of values along the height/depth axis of the fence. + Each trace has equal number of samples, and is a set of sample values along the depth direction of the seismic cube. The returned data * fence_traces_b64arr: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. Decoding info: [num_traces, num_trace_samples] diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 5b36e5328..2cd75726c 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -35,7 +35,7 @@ class SeismicFenceData(BaseModel): Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. - The trace is along the along length direction of the fence. + Each trace is defined to be a set of depth value samples along the length direction of the fence. `Properties:` - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. diff --git a/frontend/src/api/models/SeismicFenceData.ts b/frontend/src/api/models/SeismicFenceData.ts index 76e68e319..6edc13664 100644 --- a/frontend/src/api/models/SeismicFenceData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -8,7 +8,7 @@ import type { B64FloatArray } from './B64FloatArray'; * Definition of a fence of seismic data from a set of (x, y) coordinates in domain coordinate system. * Each (x, y) point provides a trace perpendicular to the x-y plane, with number of samples equal to the depth of the seismic cube. * - * The trace is along the along length direction of the fence. + * Each trace is defined to be a set of depth value samples along the length direction of the fence. * * `Properties:` * - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index 64e0db08e..6df9a5ab3 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -42,7 +42,7 @@ export class SeismicService { * Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. * * The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. - * Each trace has number of samples equal length, and is a set of values along the height/depth axis of the fence. + * Each trace has equal number of samples, and is a set of sample values along the depth direction of the seismic cube. * * The returned data * * fence_traces_b64arr: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. Decoding info: [num_traces, num_trace_samples] From 92786f1a8c93bbb542ba896a7ba6781ca0b3ec4d Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 9 Nov 2023 14:36:21 +0100 Subject: [PATCH 30/35] Adjustments after review - Handle fillValue better - Correct bug for xAxisOffset, as it was extending curtain too much. - Ensure update of color palette triggers re-render of image --- backend/src/services/vds_access/vds_access.py | 22 ++++++-- .../utils/esvIntersectionControllerUtils.ts | 9 +-- .../utils/esvIntersectionDataConversion.ts | 55 ++++++++++++------- .../src/modules/SeismicIntersection/view.tsx | 26 +++++---- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index 352c06612..f020a7cb6 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -45,11 +45,12 @@ class VdsAccess: Note that we are not providing the service with the actual vds file, but rather a SAS token and an URL to the vds file. """ - def __init__(self, sas_token: str, vds_url: str) -> None: - self.sas: str = sas_token - self.vds_url: str = vds_url - - self._interpolation = VdsInterpolation.LINEAR + def __init__( + self, sas_token: str, vds_url: str, interpolation_method: VdsInterpolation = VdsInterpolation.LINEAR + ) -> None: + self.sas = sas_token + self.vds_url = vds_url + self._interpolation = interpolation_method @staticmethod async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response: @@ -90,6 +91,8 @@ async def get_flattened_fence_traces_array_and_metadata( With traces perpendicular to the x-y plane, the traces are defined to go along the depth direction of the fence. + Invalid values, e.g. values for points outside of the seismic cube, are set to np.nan. + `Returns:` `Tuple[flattened_fence_traces_array: NDArray[np.float32], num_traces: int, num_trace_samples: int]` @@ -132,7 +135,11 @@ async def get_flattened_fence_traces_array_and_metadata( """ endpoint = "fence" - hard_coded_fill_value = -999 + + # Temporary hard coded fill value for points outside of the seismic cube. + # If no fill value is provided in the request is rejected with error if list of coordinates + # contain points outside of the seismic cube. + hard_coded_fill_value = -999.25 fence_request = VdsFenceRequest( vds=self.vds_url, @@ -175,4 +182,7 @@ async def get_flattened_fence_traces_array_and_metadata( # Flattened array with row major order, i.e. C-order in numpy flattened_fence_traces_float32_array = bytes_to_flatten_ndarray_float32(byte_array, shape=metadata.shape) + # Convert every value of `hard_coded_fill_value` to np.nan + flattened_fence_traces_float32_array[flattened_fence_traces_float32_array == hard_coded_fill_value] = np.nan + return (flattened_fence_traces_float32_array, num_traces, num_trace_samples) diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts index 38251bbe5..984686428 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts @@ -67,7 +67,7 @@ export function addWellborePathLayer(controller: Controller, wellboreTrajectoryX export type SeismicLayerOptions = { curtain: number[][]; - extension: number; + xAxisOffset: number; image: ImageBitmap; dataValues: number[][]; yAxisValues: number[]; @@ -77,12 +77,13 @@ export type SeismicLayerOptions = { */ export function addSeismicLayer( controller: Controller, - { curtain, extension, image, dataValues, yAxisValues }: SeismicLayerOptions + { curtain, xAxisOffset, image, dataValues, yAxisValues }: SeismicLayerOptions ): void { const info = getSeismicInfo({ datapoints: dataValues, yAxisValues }, curtain); if (info) { - info.minX = info.minX - extension; - info.maxX = info.maxX - extension; + // Adjust x axis offset to account for curtain + info.minX = info.minX - xAxisOffset; + info.maxX = info.maxX - xAxisOffset; } const layer = new SeismicCanvasLayer("seismic", { order: 1, diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts index ffefec189..b5731dec0 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts @@ -6,28 +6,34 @@ import { SeismicFenceData_trans } from "./queryDataTransforms"; /** * Utility to make extended trajectory object from array of 3D trajectory coordinates [x,y,z] and extension * - * TODO: Number of samples. Needs some thought for future + * TODO: Number of samples. Needs some thought for future, perhaps detect num samples based on seismic metadata? */ export function makeExtendedTrajectoryFromTrajectoryXyzPoints( trajectoryXyzPoints: number[][], extension: number, samplingIncrementMeters = 5 ): Trajectory { - if (isVerticalTrajectory(trajectoryXyzPoints)) { - trajectoryXyzPoints = addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints); + const isVertical = isVerticalTrajectory(trajectoryXyzPoints); + if (isVertical) { + // Adds extension to top and bottom of vertical line + trajectoryXyzPoints = addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints, extension); } const referenceSystem = new IntersectionReferenceSystem(trajectoryXyzPoints); - referenceSystem.offset = trajectoryXyzPoints[0][2]; // Offset should be md at start of path - const displacement = referenceSystem.displacement || 1; + // Offset: md at start of well path + referenceSystem.offset = trajectoryXyzPoints[0][2]; + const displacement = referenceSystem.displacement ?? 1; const numPoints = Math.min(1000, Math.floor((displacement + extension * 2) / samplingIncrementMeters)); - const extendedTrajectory = referenceSystem.getExtendedTrajectory(numPoints, extension, extension); - extendedTrajectory.points = extendedTrajectory.points.map((point) => [ - parseFloat(point[0].toFixed(3)), - parseFloat(point[1].toFixed(3)), - ]); + const extendedTrajectory = isVertical + ? referenceSystem.getTrajectory(numPoints) + : referenceSystem.getExtendedTrajectory(numPoints, extension, extension); + + extendedTrajectory.points.forEach((point) => { + point[0] = parseFloat(point[0].toFixed(3)); + point[1] = parseFloat(point[1].toFixed(3)); + }); return extendedTrajectory; } @@ -67,7 +73,10 @@ function isVerticalTrajectory(trajectoryXyzPoints: number[][]): boolean { * * @param trajectoryXyzPoints - Array of 3D coordinates [x,y,z] */ -function addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints: number[][]): number[][] { +function addStartAndEndPointsToTrajectoryForVerticalLine( + trajectoryXyzPoints: number[][], + extension: number +): number[][] { if (trajectoryXyzPoints.length === 0) return []; const firstCoordinates = trajectoryXyzPoints[0]; @@ -81,15 +90,18 @@ function addStartAndEndPointsToTrajectoryForVerticalLine(trajectoryXyzPoints: nu // Compare x (index 0) and y (index 1) coordinates of first and last points // Add start and end coordinates to trajectory - const addCoordAtStart = firstCoordinates[0] - 100; - const addCoordAtEnd = lastCoordinates[0] + 100; - const addCoordAtStart2 = firstCoordinates[1] - 100; - const addCoordAtEnd2 = lastCoordinates[1] + 100; + // NOTE: Should be consider to create a 3D vector with length = extension, i.e. extension = sqrt(x^2 + y^2 + z^2), with z constant, + // i.e. -> x = sqrt(extension) and y = sqrt(extension)? + const firstXCoord = firstCoordinates[0] - extension; + const firstYCoord = firstCoordinates[1]; const firstZCoord = firstCoordinates[2]; + + const lastXCoord = lastCoordinates[0] + extension; + const lastYCoord = lastCoordinates[1]; const lastZCoord = lastCoordinates[2]; - modifiedTrajectoryXyzPoints.unshift([addCoordAtStart, addCoordAtStart2, firstZCoord]); - modifiedTrajectoryXyzPoints.push([addCoordAtEnd, addCoordAtEnd2, lastZCoord]); + modifiedTrajectoryXyzPoints.unshift([firstXCoord, firstYCoord, firstZCoord]); + modifiedTrajectoryXyzPoints.push([lastXCoord, lastYCoord, lastZCoord]); return modifiedTrajectoryXyzPoints; } @@ -148,7 +160,10 @@ export function makeReferenceSystemFromTrajectoryXyzPoints( * a2 b2 c2 d2 * a3 b3 c3 d3 */ -export function createSeismicSliceImageDataArrayFromFenceData(fenceData: SeismicFenceData_trans): number[][] { +export function createSeismicSliceImageDataArrayFromFenceData( + fenceData: SeismicFenceData_trans, + fillValue = 0 +): number[][] { const imageArray: number[][] = []; const numTraces = fenceData.num_traces; @@ -159,7 +174,9 @@ export function createSeismicSliceImageDataArrayFromFenceData(fenceData: Seismic const row: number[] = []; for (let j = 0; j < numTraces; ++j) { const index = i + j * numSamples; - row.push(fenceValues[index]); + const fenceValue = fenceValues[index]; + const validFenceValue = Number.isNaN(fenceValue) ? fillValue : fenceValue; + row.push(validFenceValue); } imageArray.push(row); } diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index 99708c3f4..2d42de8f3 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -57,7 +57,11 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) const seismicColorScale = workbenchSettings.useDiscreteColorScale({ gradientType: ColorScaleGradientType.Diverging, }); - const seismicColors = seismicColorScale.getColorPalette().getColors(); + + const [seismicColors, setSeismicColors] = React.useState(seismicColorScale.getColorPalette().getColors()); + if (!isEqual(seismicColorScale.getColorPalette().getColors(), seismicColors)) { + setSeismicColors(seismicColorScale.getColorPalette().getColors()); + } // Extended wellbore trajectory for creating intersection/fence extended on both sides of wellbore const [extendedWellboreTrajectory, setExtendedWellboreTrajectory] = React.useState(null); @@ -118,12 +122,8 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) if (!isEqual(newExtendedWellboreTrajectory, extendedWellboreTrajectory)) { setExtendedWellboreTrajectory(newExtendedWellboreTrajectory); - const x_points = newExtendedWellboreTrajectory - ? newExtendedWellboreTrajectory.points.map((coord) => coord[0]) - : []; - const y_points = newExtendedWellboreTrajectory - ? newExtendedWellboreTrajectory.points.map((coord) => coord[1]) - : []; + const x_points = newExtendedWellboreTrajectory?.points.map((coord) => coord[0]) ?? []; + const y_points = newExtendedWellboreTrajectory?.points.map((coord) => coord[1]) ?? []; setSeismicFencePolyline({ x_points, y_points }); } @@ -158,7 +158,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) if (!seismicFenceDataQuery.data) return; // Get an array of projected 2D points [x, y], as 2D curtain projection from a set of trajectory 3D points and offset - const newExtendedWellboreTrajectoryXyProjection: number[][] | null = extendedWellboreTrajectory + const newExtendedWellboreTrajectoryXyProjection: number[][] = extendedWellboreTrajectory ? IntersectionReferenceSystem.toDisplacement( extendedWellboreTrajectory.points, extendedWellboreTrajectory.offset @@ -216,7 +216,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) } setRenderWellboreTrajectoryXyzPoints(newRenderWellboreTrajectoryXyzPoints); }, - [seismicFenceDataQuery.data, extendedWellboreTrajectory, getWellTrajectoriesQuery.data] + [seismicFenceDataQuery.data, extendedWellboreTrajectory, getWellTrajectoriesQuery.data, seismicColors] ); // Update esv-intersection controller when data is ready - keep old data to prevent blank view when fetching new data @@ -226,10 +226,14 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) addWellborePathLayer(esvIntersectionControllerRef.current, renderWellboreTrajectoryXyzPoints); - if (seismicLayerData && seismicFenceImageBitmapAndStatus.image) { + if ( + seismicLayerData && + seismicFenceImageBitmapAndStatus.image && + seismicFenceImageBitmapAndStatus.status === SeismicImageBitmapStatus.VALID + ) { addSeismicLayer(esvIntersectionControllerRef.current, { curtain: seismicLayerData.trajectoryXyProjection, - extension: extension, + xAxisOffset: extension, image: seismicFenceImageBitmapAndStatus.image, dataValues: seismicLayerData.seismicImageDataArray, yAxisValues: seismicLayerData.seismicImageYAxisValues, From 18e41b9f379dce777922c664bdc794246de9dc52 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Fri, 10 Nov 2023 08:37:21 +0100 Subject: [PATCH 31/35] Adjust query cacheTime -> gcTime for useQuery --- frontend/src/modules/SeismicIntersection/queryHooks.tsx | 4 ++-- frontend/src/modules/_shared/WellBore/queryHooks.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/modules/SeismicIntersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx index 30560eead..b04a94004 100644 --- a/frontend/src/modules/SeismicIntersection/queryHooks.tsx +++ b/frontend/src/modules/SeismicIntersection/queryHooks.tsx @@ -15,7 +15,7 @@ export function useSeismicCubeDirectoryQuery( queryKey: ["getSeismicDirectory", caseUuid, ensembleName], queryFn: () => apiService.seismic.getSeismicDirectory(caseUuid ?? "", ensembleName ?? ""), staleTime: STALE_TIME, - cacheTime: CACHE_TIME, + gcTime: CACHE_TIME, enabled: !!(caseUuid && ensembleName), }); } @@ -54,7 +54,7 @@ export function useSeismicFenceDataQuery( ), select: transformSeismicFenceData, staleTime: STALE_TIME, - cacheTime: CACHE_TIME, + gcTime: CACHE_TIME, enabled: !!( allowEnable && caseUuid && diff --git a/frontend/src/modules/_shared/WellBore/queryHooks.ts b/frontend/src/modules/_shared/WellBore/queryHooks.ts index 0d9bc38fe..453092476 100644 --- a/frontend/src/modules/_shared/WellBore/queryHooks.ts +++ b/frontend/src/modules/_shared/WellBore/queryHooks.ts @@ -10,7 +10,7 @@ export function useGetWellHeaders(caseUuid: string | undefined): UseQueryResult< queryKey: ["getWellHeaders", caseUuid], queryFn: () => apiService.well.getWellHeaders(caseUuid ?? ""), staleTime: STALE_TIME, - cacheTime: STALE_TIME, + gcTime: CACHE_TIME, enabled: caseUuid ? true : false, }); } @@ -20,7 +20,7 @@ export function useGetFieldWellsTrajectories(caseUuid: string | undefined): UseQ queryKey: ["getFieldWellsTrajectories", caseUuid], queryFn: () => apiService.well.getFieldWellTrajectories(caseUuid ?? ""), staleTime: STALE_TIME, - cacheTime: CACHE_TIME, + gcTime: CACHE_TIME, enabled: caseUuid ? true : false, }); } @@ -30,7 +30,7 @@ export function useGetWellTrajectories(wellUuids: string[] | undefined): UseQuer queryKey: ["getWellTrajectories", wellUuids], queryFn: () => apiService.well.getWellTrajectories(wellUuids ?? []), staleTime: STALE_TIME, - cacheTime: CACHE_TIME, + gcTime: CACHE_TIME, enabled: wellUuids ? true : false, }); } From 21ca0797dde878c9e09b8825af71563ee287f223 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Fri, 10 Nov 2023 14:39:49 +0100 Subject: [PATCH 32/35] Refactor async image generation into hook --- .../utils/esvIntersectionHooks.ts | 53 ++++++ .../src/modules/SeismicIntersection/view.tsx | 154 ++++++------------ 2 files changed, 105 insertions(+), 102 deletions(-) create mode 100644 frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts new file mode 100644 index 000000000..c5206a547 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts @@ -0,0 +1,53 @@ +import React from "react"; + +import { generateSeismicSliceImage } from "@equinor/esv-intersection"; + +import { isEqual } from "lodash"; + +export type SeismicSliceImageOptions = { + dataValues: number[][]; // Array of seismic image data values + yAxisValues: number[]; // Array of seismic image y axis values + trajectoryXyPoints: number[][]; // Array of 2D projected points [x, y] + colormap: string[]; + extension: number; // Needed to keep synched extension +}; + +export enum SeismicSliceImageStatus { + SUCCESS = "success", + LOADING = "loading", + ERROR = "error", +} + +export function useGenerateSeismicSliceImage(data: SeismicSliceImageOptions | null) { + const [prevData, setPrevData] = React.useState(null); + const [image, setImage] = React.useState(null); + const [imageStatus, setImageStatus] = React.useState(SeismicSliceImageStatus.SUCCESS); + const [synchedImageOptions, setSynchedImageOptions] = React.useState(null); + + if (data !== null && !isEqual(data, prevData)) { + setPrevData(data); + setImageStatus(SeismicSliceImageStatus.LOADING); + + // Async generation of seismic slice image + generateSeismicSliceImage( + { datapoints: data.dataValues, yAxisValues: data.yAxisValues }, + data.trajectoryXyPoints, + data.colormap, + { + isLeftToRight: true, + } + ) + .then((result) => { + setImage(result ?? null); + setImageStatus(SeismicSliceImageStatus.SUCCESS); + setSynchedImageOptions(data); + }) + .catch(() => { + setImage(null); + setImageStatus(SeismicSliceImageStatus.ERROR); + setSynchedImageOptions(data); + }); + } + + return { image: image, synchedOptions: synchedImageOptions, status: imageStatus }; +} diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index 2d42de8f3..4362ae2e2 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -1,13 +1,7 @@ import React, { useId } from "react"; import { SeismicFencePolyline_api } from "@api"; -import { - Controller, - GridLayer, - IntersectionReferenceSystem, - Trajectory, - generateSeismicSliceImage, -} from "@equinor/esv-intersection"; +import { Controller, GridLayer, IntersectionReferenceSystem, Trajectory } from "@equinor/esv-intersection"; import { ModuleFCProps } from "@framework/Module"; import { useViewStatusWriter } from "@framework/StatusWriter"; import { useElementSize } from "@lib/hooks/useElementSize"; @@ -27,18 +21,11 @@ import { makeReferenceSystemFromTrajectoryXyzPoints, makeTrajectoryXyzPointsFromWellboreTrajectory, } from "./utils/esvIntersectionDataConversion"; - -enum SeismicImageBitmapStatus { - ERROR = "error", - INVALID = "invalid", - VALID = "valid", -} - -type SeismicLayerData = { - trajectoryXyProjection: number[][]; // Array of 2D projected points [x, y] - seismicImageDataArray: number[][]; // Array of seismic image data values - seismicImageYAxisValues: number[]; // Array of seismic image y axis values -}; +import { + SeismicSliceImageOptions, + SeismicSliceImageStatus, + useGenerateSeismicSliceImage, +} from "./utils/esvIntersectionHooks"; export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { const wrapperDivRef = React.useRef(null); @@ -73,11 +60,11 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Data for seismic fence layer in esv-intersection const [seismicFencePolyline, setSeismicFencePolyline] = React.useState(null); - const [seismicLayerData, setSeismicLayerData] = React.useState(null); - const [seismicFenceImageBitmapAndStatus, setSeismicFenceImageBitmapAndStatus] = React.useState<{ - image: ImageBitmap | null; - status: SeismicImageBitmapStatus; - }>({ image: null, status: SeismicImageBitmapStatus.INVALID }); + + // Async generating seismic slice image + const [generateSeismicSliceImageOptions, setGenerateSeismicSliceImageOptions] = + React.useState(null); + const generateSeismicSliceImageHook = useGenerateSeismicSliceImage(generateSeismicSliceImageOptions); React.useEffect(function initializeEsvIntersectionController() { if (esvIntersectionContainerRef.current) { @@ -107,6 +94,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) } // Use first trajectory and create polyline for seismic fence query, and extended wellbore trajectory for generating seismic fence image + let candidateSeismicFencePolyline = seismicFencePolyline; if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { const trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(getWellTrajectoriesQuery.data[0]); const newExtendedWellboreTrajectory = makeExtendedTrajectoryFromTrajectoryXyzPoints( @@ -119,18 +107,20 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) esvIntersectionControllerRef.current.setReferenceSystem(referenceSystem); } + // If the new extended trajectory is different, update the polyline, but keep the seismic fence image if (!isEqual(newExtendedWellboreTrajectory, extendedWellboreTrajectory)) { setExtendedWellboreTrajectory(newExtendedWellboreTrajectory); const x_points = newExtendedWellboreTrajectory?.points.map((coord) => coord[0]) ?? []; const y_points = newExtendedWellboreTrajectory?.points.map((coord) => coord[1]) ?? []; - setSeismicFencePolyline({ x_points, y_points }); + candidateSeismicFencePolyline = { x_points, y_points }; + setSeismicFencePolyline(candidateSeismicFencePolyline); } // When new well trajectory 3D points are loaded, update the render trajectory and clear the seismic fence image if (!isEqual(trajectoryXyzPoints, renderWellboreTrajectoryXyzPoints)) { setRenderWellboreTrajectoryXyzPoints(trajectoryXyzPoints); - setSeismicLayerData(null); + setGenerateSeismicSliceImageOptions(null); } } @@ -142,82 +132,39 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) seismicAddress?.attribute ?? null, seismicAddress?.timeString ?? null, seismicAddress?.observed ?? null, - seismicFencePolyline, + candidateSeismicFencePolyline, seismicAddress !== null ); if (seismicFenceDataQuery.isError) { statusWriter.addError("Error loading seismic fence data"); } - // Regenerate seismic fence image when fence data changes - // - Must be useEffect due to async generateSeismicSliceImage function - // - seismicFenceDataQuery.data in dependency array: Assumes useQuery provides same reference as long as the query data is the same - // (https://github.com/TanStack/query/commit/89bec2039324282a023e4e726ea6ae2e1c45178a) - React.useEffect( - function generateSeismicFenceImageLayerData() { - if (!seismicFenceDataQuery.data) return; - - // Get an array of projected 2D points [x, y], as 2D curtain projection from a set of trajectory 3D points and offset - const newExtendedWellboreTrajectoryXyProjection: number[][] = extendedWellboreTrajectory - ? IntersectionReferenceSystem.toDisplacement( - extendedWellboreTrajectory.points, - extendedWellboreTrajectory.offset - ) - : []; - - const newSeismicImageDataArray = createSeismicSliceImageDataArrayFromFenceData(seismicFenceDataQuery.data); - const newSeismicImageYAxisValues = createSeismicSliceImageYAxisValuesArrayFromFenceData( - seismicFenceDataQuery.data - ); - - const imageDataPoints = newSeismicImageDataArray; - const yAxisValues = newSeismicImageYAxisValues; - const trajectory = newExtendedWellboreTrajectoryXyProjection; - - // Note: No cache, thereby the image is regenerated when switching back and forth - generateSeismicSliceImage( - { datapoints: imageDataPoints, yAxisValues: yAxisValues }, - trajectory, - seismicColors, - { - isLeftToRight: true, - } - ) - .then((image) => { - if (!image) { - setSeismicFenceImageBitmapAndStatus({ - image: null, - status: SeismicImageBitmapStatus.INVALID, - }); - return; - } - setSeismicFenceImageBitmapAndStatus({ - image: image, - status: SeismicImageBitmapStatus.VALID, - }); - }) - .catch(() => - setSeismicFenceImageBitmapAndStatus({ image: null, status: SeismicImageBitmapStatus.ERROR }) - ); + if (seismicFenceDataQuery.data) { + // Get an array of projected 2D points [x, y], as 2D curtain projection from a set of trajectory 3D points and offset + const newExtendedWellboreTrajectoryXyProjection: number[][] = extendedWellboreTrajectory + ? IntersectionReferenceSystem.toDisplacement( + extendedWellboreTrajectory.points, + extendedWellboreTrajectory.offset + ) + : []; + + const newSeismicImageDataArray = createSeismicSliceImageDataArrayFromFenceData(seismicFenceDataQuery.data); + const newSeismicImageYAxisValues = createSeismicSliceImageYAxisValuesArrayFromFenceData( + seismicFenceDataQuery.data + ); - // Update calculated seismic data - setSeismicLayerData({ - trajectoryXyProjection: newExtendedWellboreTrajectoryXyProjection, - seismicImageDataArray: newSeismicImageDataArray, - seismicImageYAxisValues: newSeismicImageYAxisValues, - }); + const newGenerateSeismicSliceImageOptions: SeismicSliceImageOptions = { + dataValues: newSeismicImageDataArray, + yAxisValues: newSeismicImageYAxisValues, + trajectoryXyPoints: newExtendedWellboreTrajectoryXyProjection, + colormap: seismicColors, + extension: extension, + }; - // Update wellbore trajectory - let newRenderWellboreTrajectoryXyzPoints: number[][] | null = null; - if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { - newRenderWellboreTrajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory( - getWellTrajectoriesQuery.data[0] - ); - } - setRenderWellboreTrajectoryXyzPoints(newRenderWellboreTrajectoryXyzPoints); - }, - [seismicFenceDataQuery.data, extendedWellboreTrajectory, getWellTrajectoriesQuery.data, seismicColors] - ); + if (!isEqual(generateSeismicSliceImageOptions, newGenerateSeismicSliceImageOptions)) { + setGenerateSeismicSliceImageOptions(newGenerateSeismicSliceImageOptions); + } + } // Update esv-intersection controller when data is ready - keep old data to prevent blank view when fetching new data if (esvIntersectionControllerRef.current && renderWellboreTrajectoryXyzPoints) { @@ -227,16 +174,17 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) addWellborePathLayer(esvIntersectionControllerRef.current, renderWellboreTrajectoryXyzPoints); if ( - seismicLayerData && - seismicFenceImageBitmapAndStatus.image && - seismicFenceImageBitmapAndStatus.status === SeismicImageBitmapStatus.VALID + generateSeismicSliceImageOptions && + generateSeismicSliceImageHook.synchedOptions && + generateSeismicSliceImageHook.image && + generateSeismicSliceImageHook.status === SeismicSliceImageStatus.SUCCESS ) { addSeismicLayer(esvIntersectionControllerRef.current, { - curtain: seismicLayerData.trajectoryXyProjection, - xAxisOffset: extension, - image: seismicFenceImageBitmapAndStatus.image, - dataValues: seismicLayerData.seismicImageDataArray, - yAxisValues: seismicLayerData.seismicImageYAxisValues, + curtain: generateSeismicSliceImageHook.synchedOptions.trajectoryXyPoints, + xAxisOffset: generateSeismicSliceImageHook.synchedOptions.extension, + image: generateSeismicSliceImageHook.image, + dataValues: generateSeismicSliceImageHook.synchedOptions.dataValues, + yAxisValues: generateSeismicSliceImageHook.synchedOptions.yAxisValues, }); } @@ -257,6 +205,8 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) Error loading seismic fence data ) : getWellTrajectoriesQuery.isError ? ( Error loading well trajectories + ) : generateSeismicSliceImageHook.status === SeismicSliceImageStatus.ERROR ? ( + Error generating seismic slice image ) : (
)} From b7591d097c58a1a97863b59291b2da794396415d Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 30 Nov 2023 14:55:30 +0100 Subject: [PATCH 33/35] Fixes based on review Back-end: - Rename @router.post method to `post_get` - Add async to function names - Renamed "seismicDirectory" to "seismicCubeMetaList". - Update attribute names for clarification - Update doc for clarification Front-end: - Adjust iso date/interval strings conversion - Adjust "seismic directory" -> "seismic cube meta list" naming. - Improve naming and typing for readability --- .../backend/primary/routers/seismic/router.py | 32 +++--- .../primary/routers/seismic/schemas.py | 9 +- .../services/sumo_access/seismic_access.py | 10 +- backend/src/services/vds_access/vds_access.py | 28 ++--- frontend/src/api/index.ts | 2 +- ...ence.ts => Body_post_get_seismic_fence.ts} | 2 +- frontend/src/api/models/SeismicFenceData.ts | 9 +- frontend/src/api/services/SeismicService.ts | 26 ++--- frontend/src/modules/Grid3D/settings.tsx | 4 +- frontend/src/modules/Grid3D/view.tsx | 4 +- .../SeismicIntersection/loadModule.tsx | 2 +- .../SeismicIntersection/queryHooks.tsx | 14 +-- .../modules/SeismicIntersection/settings.tsx | 104 ++++++++++++------ .../utils/esvIntersectionControllerUtils.ts | 37 ++++--- .../utils/esvIntersectionDataConversion.ts | 6 +- .../utils/esvIntersectionHooks.ts | 28 +++-- .../utils/seismicCubeDirectory.ts | 22 ++-- .../src/modules/SeismicIntersection/view.tsx | 41 ++++--- .../src/modules/SubsurfaceMap/settings.tsx | 4 +- frontend/src/modules/SubsurfaceMap/view.tsx | 6 +- .../src/modules/_shared/WellBore/index.ts | 2 +- .../modules/_shared/WellBore/queryHooks.ts | 6 +- 22 files changed, 221 insertions(+), 177 deletions(-) rename frontend/src/api/models/{Body_get_seismic_fence.ts => Body_post_get_seismic_fence.ts} (80%) diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend/src/backend/primary/routers/seismic/router.py index 162fb470d..90b037e50 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend/src/backend/primary/routers/seismic/router.py @@ -19,25 +19,25 @@ router = APIRouter() -@router.get("/seismic_directory/") -async def get_seismic_directory( +@router.get("/seismic_cube_meta_list/") +async def get_seismic_cube_meta_list( 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. + Get a list of seismic cube meta. """ access = await SeismicAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - seismic_cube_metas = await access.get_seismic_directory() + seismic_cube_meta_list = await access.get_seismic_cube_meta_list_async() try: - return [schemas.SeismicCubeMeta(**meta.__dict__) for meta in seismic_cube_metas] + return [schemas.SeismicCubeMeta(**meta.__dict__) for meta in seismic_cube_meta_list] except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc -@router.post("/seismic_fence/") -async def get_seismic_fence( +@router.post("/get_seismic_fence/") +async def post_get_seismic_fence( authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), @@ -52,12 +52,8 @@ async def get_seismic_fence( The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. Each trace has equal number of samples, and is a set of sample values along the depth direction of the seismic cube. - The returned data - * fence_traces_b64arr: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. Decoding info: [num_traces, num_trace_samples] - * num_traces: Number of traces in fence array - * num_trace_samples: Number of samples in each trace - * min_fence_depth: The minimum depth value of the fence. - * max_fence_depth: The maximum depth value of the fence. + Returns: + A SeismicFenceData object with fence traces in encoded 1D array, metadata for trace array decoding and fence min/max depth. """ seismic_access = await SeismicAccess.from_case_uuid( authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name @@ -65,7 +61,7 @@ async def get_seismic_fence( vds_handle: Optional[VdsHandle] = None try: - vds_handle = await seismic_access.get_vds_handle( + vds_handle = await seismic_access.get_vds_handle_async( realization=realization_num, seismic_attribute=seismic_attribute, time_or_interval_str=time_or_interval_str, @@ -84,13 +80,13 @@ async def get_seismic_fence( [ flattened_fence_traces_array, num_traces, - num_trace_samples, - ] = await vds_access.get_flattened_fence_traces_array_and_metadata( + num_samples_per_trace, + ] = await vds_access.get_flattened_fence_traces_array_and_metadata_async( coordinates=VdsCoordinates(polyline.x_points, polyline.y_points), coordinate_system=VdsCoordinateSystem.CDP, ) - meta: VdsMetadata = await vds_access.get_metadata() + meta: VdsMetadata = await vds_access.get_metadata_async() if len(meta.axis) != 3: raise ValueError(f"Expected 3 axes, got {len(meta.axis)}") depth_axis_meta = meta.axis[2] @@ -98,7 +94,7 @@ async def get_seismic_fence( return schemas.SeismicFenceData( fence_traces_b64arr=b64_encode_float_array_as_float32(flattened_fence_traces_array), num_traces=num_traces, - num_trace_samples=num_trace_samples, + num_samples_per_trace=num_samples_per_trace, min_fence_depth=depth_axis_meta.min, max_fence_depth=depth_axis_meta.max, ) diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend/src/backend/primary/routers/seismic/schemas.py index 2cd75726c..3e2c4b087 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend/src/backend/primary/routers/seismic/schemas.py @@ -40,16 +40,15 @@ class SeismicFenceData(BaseModel): `Properties:` - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline. - - `num_trace_samples`: The number of samples in each trace. + - `num_samples_per_trace`: The number of samples in each trace. - `min_fence_depth`: The minimum depth value of the fence. - `max_fence_depth`: The maximum depth value of the fence. `Description - fence_traces_b64arr:`\n The encoded fence trace array is a flattened array of traces, where data is stored trace by trace. - With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. + With `m = num_traces`, and `n = num_samples_per_trace`, the flattened array has length `mxn`. - Fence traces 1D array: [trace_1, trace_2, ..., trace_m] \n - trace_1, trace_2, ... , trace_m are 1D arrays: [sample_1, sample_2, ..., sample_n] + Fence traces 1D array: [trace_1_sample_1, trace_1_sample_2, ..., trace_1_sample_n, ..., trace_m_sample_1, trace_m_sample_2, ..., trace_m_sample_n] \n See: - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 @@ -57,6 +56,6 @@ class SeismicFenceData(BaseModel): fence_traces_b64arr: B64FloatArray num_traces: int - num_trace_samples: int + num_samples_per_trace: int min_fence_depth: float max_fence_depth: float diff --git a/backend/src/services/sumo_access/seismic_access.py b/backend/src/services/sumo_access/seismic_access.py index 25245fb21..3e4cea0b5 100644 --- a/backend/src/services/sumo_access/seismic_access.py +++ b/backend/src/services/sumo_access/seismic_access.py @@ -12,9 +12,9 @@ class SeismicAccess(SumoEnsemble): - async def get_seismic_directory(self) -> List[SeismicCubeMeta]: + async def get_seismic_cube_meta_list_async(self) -> List[SeismicCubeMeta]: seismic_cube_collection: CubeCollection = self._case.cubes.filter(iteration=self._iteration_name, realization=0) - seismic_cube_metas: List[SeismicCubeMeta] = [] + seismic_cube_meta_list: List[SeismicCubeMeta] = [] async 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) @@ -34,10 +34,10 @@ async def get_seismic_directory(self) -> List[SeismicCubeMeta]: is_observation=cube["data"]["is_observation"], is_depth=cube["data"]["vertical_domain"] == "depth", ) - seismic_cube_metas.append(seismic_meta) - return seismic_cube_metas + seismic_cube_meta_list.append(seismic_meta) + return seismic_cube_meta_list - async def get_vds_handle( + async def get_vds_handle_async( self, seismic_attribute: str, realization: int, diff --git a/backend/src/services/vds_access/vds_access.py b/backend/src/services/vds_access/vds_access.py index f020a7cb6..af79d88ba 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend/src/services/vds_access/vds_access.py @@ -53,7 +53,7 @@ def __init__( self._interpolation = interpolation_method @staticmethod - async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response: + async def _query_async(endpoint: str, request: VdsRequestedResource) -> httpx.Response: """Query the service""" async with httpx.AsyncClient() as client: @@ -69,17 +69,17 @@ async def _query(endpoint: str, request: VdsRequestedResource) -> httpx.Response return response - async def get_metadata(self) -> VdsMetadata: + async def get_metadata_async(self) -> VdsMetadata: """Gets metadata from the cube""" endpoint = "metadata" metadata_request = VdsMetadataRequest(vds=self.vds_url, sas=self.sas) - response = await self._query(endpoint, metadata_request) + response = await self._query_async(endpoint, metadata_request) metadata = response.json() return VdsMetadata(**metadata) - async def get_flattened_fence_traces_array_and_metadata( + async def get_flattened_fence_traces_array_and_metadata_async( self, coordinates: VdsCoordinates, coordinate_system: VdsCoordinateSystem = VdsCoordinateSystem.CDP ) -> Tuple[NDArray[np.float32], int, int]: """ @@ -94,18 +94,18 @@ async def get_flattened_fence_traces_array_and_metadata( Invalid values, e.g. values for points outside of the seismic cube, are set to np.nan. `Returns:` - `Tuple[flattened_fence_traces_array: NDArray[np.float32], num_traces: int, num_trace_samples: int]` + `Tuple[flattened_fence_traces_array: NDArray[np.float32], num_traces: int, num_samples_per_trace: int]` - `flattened_fence_traces_array`: 1D np.ndarray with dtype=float32, stored trace by trace. The array has length `num_traces x num_trace_samples`.\n + `flattened_fence_traces_array`: 1D np.ndarray with dtype=float32, stored trace by trace. The array has length `num_traces x num_samples_per_trace`.\n `num_traces`: number of traces along the length of the fence, i.e. number of (x, y) coordinates.\n - `num_trace_samples`: number of samples in each trace, i.e. number of values along the height/depth axis of the fence.\n + `num_samples_per_trace`: number of samples in each trace, i.e. number of values along the height/depth axis of the fence.\n \n`Description:` - With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. + With `m = num_traces`, and `n = num_samples_per_trace`, the flattened array has length `mxn`. - `2D Fence Trace Array:` + `2D Fence Trace Array from VDS-slice query:` ``` [[t11, t12, ..., t1n], @@ -114,7 +114,7 @@ async def get_flattened_fence_traces_array_and_metadata( [tm1, tm2, ..., tmn]] ``` - \n`Flattened 2D trace array with row major order:` + \n`Returned flattened 2D trace array with row major order:` ``` [t11, t12, ..., t1n, t21, t22, ..., t2n, ..., tm1, tm2, ..., tmn] @@ -151,7 +151,7 @@ async def get_flattened_fence_traces_array_and_metadata( ) # Fence query returns two parts - metadata and data - response = await self._query(endpoint, fence_request) + response = await self._query_async(endpoint, fence_request) # Use MultipartDecoder with httpx's Response content and headers decoder = MultipartDecoder(content=response.content, content_type=response.headers["Content-Type"]) @@ -175,9 +175,9 @@ async def get_flattened_fence_traces_array_and_metadata( raise ValueError(f"Expected shape to be 2D, got {metadata.shape}") # fence array data: [[t11, t12, ..., t1n], [t21, t22, ..., t2n], ..., [tm1, tm2, ..., tmn]] - # m = num_traces, n = num_trace_samples + # m = num_traces, n = num_samples_per_trace num_traces = metadata.shape[0] - num_trace_samples = metadata.shape[1] + num_samples_per_trace = metadata.shape[1] # Flattened array with row major order, i.e. C-order in numpy flattened_fence_traces_float32_array = bytes_to_flatten_ndarray_float32(byte_array, shape=metadata.shape) @@ -185,4 +185,4 @@ async def get_flattened_fence_traces_array_and_metadata( # Convert every value of `hard_coded_fill_value` to np.nan flattened_fence_traces_float32_array[flattened_fence_traces_float32_array == hard_coded_fill_value] = np.nan - return (flattened_fence_traces_float32_array, num_traces, num_trace_samples) + return (flattened_fence_traces_float32_array, num_traces, num_samples_per_trace) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 041228429..1b6815b9e 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -12,7 +12,7 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export { B64FloatArray as B64FloatArray_api } from './models/B64FloatArray'; export { B64UintArray as B64UintArray_api } from './models/B64UintArray'; export type { Body_get_realizations_response as Body_get_realizations_response_api } from './models/Body_get_realizations_response'; -export type { Body_get_seismic_fence as Body_get_seismic_fence_api } from './models/Body_get_seismic_fence'; +export type { Body_post_get_seismic_fence as Body_post_get_seismic_fence_api } from './models/Body_post_get_seismic_fence'; export type { CaseInfo as CaseInfo_api } from './models/CaseInfo'; export type { Completions as Completions_api } from './models/Completions'; export type { EnsembleDetails as EnsembleDetails_api } from './models/EnsembleDetails'; diff --git a/frontend/src/api/models/Body_get_seismic_fence.ts b/frontend/src/api/models/Body_post_get_seismic_fence.ts similarity index 80% rename from frontend/src/api/models/Body_get_seismic_fence.ts rename to frontend/src/api/models/Body_post_get_seismic_fence.ts index 6a8bb0ad2..9fcef64cf 100644 --- a/frontend/src/api/models/Body_get_seismic_fence.ts +++ b/frontend/src/api/models/Body_post_get_seismic_fence.ts @@ -4,7 +4,7 @@ import type { SeismicFencePolyline } from './SeismicFencePolyline'; -export type Body_get_seismic_fence = { +export type Body_post_get_seismic_fence = { polyline: SeismicFencePolyline; }; diff --git a/frontend/src/api/models/SeismicFenceData.ts b/frontend/src/api/models/SeismicFenceData.ts index 6edc13664..4f41ffd26 100644 --- a/frontend/src/api/models/SeismicFenceData.ts +++ b/frontend/src/api/models/SeismicFenceData.ts @@ -13,18 +13,17 @@ import type { B64FloatArray } from './B64FloatArray'; * `Properties:` * - `fence_traces_b64arr`: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. * - `num_traces`: The number of traces in the fence trace array. Equals the number of (x, y) coordinates in requested polyline. - * - `num_trace_samples`: The number of samples in each trace. + * - `num_samples_per_trace`: The number of samples in each trace. * - `min_fence_depth`: The minimum depth value of the fence. * - `max_fence_depth`: The maximum depth value of the fence. * * `Description - fence_traces_b64arr:` * * The encoded fence trace array is a flattened array of traces, where data is stored trace by trace. - * With `m = num_traces`, and `n = num_trace_samples`, the flattened array has length `mxn`. + * With `m = num_traces`, and `n = num_samples_per_trace`, the flattened array has length `mxn`. * - * Fence traces 1D array: [trace_1, trace_2, ..., trace_m] + * Fence traces 1D array: [trace_1_sample_1, trace_1_sample_2, ..., trace_1_sample_n, ..., trace_m_sample_1, trace_m_sample_2, ..., trace_m_sample_n] * - * trace_1, trace_2, ... , trace_m are 1D arrays: [sample_1, sample_2, ..., sample_n] * * See: * - VdsAxis: https://github.com/equinor/vds-slice/blob/ab6f39789bf3d3b59a8df14f1c4682d340dc0bf3/internal/core/core.go#L37-L55 @@ -32,7 +31,7 @@ import type { B64FloatArray } from './B64FloatArray'; export type SeismicFenceData = { fence_traces_b64arr: B64FloatArray; num_traces: number; - num_trace_samples: number; + num_samples_per_trace: number; min_fence_depth: number; max_fence_depth: number; }; diff --git a/frontend/src/api/services/SeismicService.ts b/frontend/src/api/services/SeismicService.ts index 6df9a5ab3..ded56e23b 100644 --- a/frontend/src/api/services/SeismicService.ts +++ b/frontend/src/api/services/SeismicService.ts @@ -1,7 +1,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Body_get_seismic_fence } from '../models/Body_get_seismic_fence'; +import type { Body_post_get_seismic_fence } from '../models/Body_post_get_seismic_fence'; import type { SeismicCubeMeta } from '../models/SeismicCubeMeta'; import type { SeismicFenceData } from '../models/SeismicFenceData'; @@ -13,20 +13,20 @@ export class SeismicService { constructor(public readonly httpRequest: BaseHttpRequest) {} /** - * Get Seismic Directory - * Get a directory of seismic cubes. + * Get Seismic Cube Meta List + * Get a list of seismic cube meta. * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @returns SeismicCubeMeta Successful Response * @throws ApiError */ - public getSeismicDirectory( + public getSeismicCubeMetaList( caseUuid: string, ensembleName: string, ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/seismic/seismic_directory/', + url: '/seismic/seismic_cube_meta_list/', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -38,18 +38,14 @@ export class SeismicService { } /** - * Get Seismic Fence + * Post Get Seismic Fence * Get a fence of seismic data from a polyline defined by a set of (x, y) coordinates in domain coordinate system. * * The fence data contains a set of traces perpendicular to the polyline, with one trace per (x, y)-point in polyline. * Each trace has equal number of samples, and is a set of sample values along the depth direction of the seismic cube. * - * The returned data - * * fence_traces_b64arr: The fence trace array is base64 encoded 1D float array - where data is stored trace by trace. Decoding info: [num_traces, num_trace_samples] - * * num_traces: Number of traces in fence array - * * num_trace_samples: Number of samples in each trace - * * min_fence_depth: The minimum depth value of the fence. - * * max_fence_depth: The maximum depth value of the fence. + * Returns: + * A SeismicFenceData object with fence traces in encoded 1D array, metadata for trace array decoding and fence min/max depth. * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param realizationNum Realization number @@ -60,18 +56,18 @@ export class SeismicService { * @returns SeismicFenceData Successful Response * @throws ApiError */ - public getSeismicFence( + public postGetSeismicFence( caseUuid: string, ensembleName: string, realizationNum: number, seismicAttribute: string, timeOrIntervalStr: string, observed: boolean, - requestBody: Body_get_seismic_fence, + requestBody: Body_post_get_seismic_fence, ): CancelablePromise { return this.httpRequest.request({ method: 'POST', - url: '/seismic/seismic_fence/', + url: '/seismic/get_seismic_fence/', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, diff --git a/frontend/src/modules/Grid3D/settings.tsx b/frontend/src/modules/Grid3D/settings.tsx index 832af05b9..a59e790ad 100644 --- a/frontend/src/modules/Grid3D/settings.tsx +++ b/frontend/src/modules/Grid3D/settings.tsx @@ -12,7 +12,7 @@ import { CircularProgress } from "@lib/components/CircularProgress"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { Label } from "@lib/components/Label"; import { Select, SelectOption } from "@lib/components/Select"; -import { useGetWellHeaders } from "@modules/_shared/WellBore/queryHooks"; +import { useWellHeadersQuery } from "@modules/_shared/WellBore/queryHooks"; import { useGridModelNames, useGridParameterNames } from "./queryHooks"; import state from "./state"; @@ -44,7 +44,7 @@ export function settings({ moduleContext, workbenchServices, workbenchSession }: const firstEnsembleName = computedEnsembleIdent?.getEnsembleName() ?? null; const gridNamesQuery = useGridModelNames(firstCaseUuid, firstEnsembleName); const parameterNamesQuery = useGridParameterNames(firstCaseUuid, firstEnsembleName, gridName); - const wellHeadersQuery = useGetWellHeaders(computedEnsembleIdent?.getCaseUuid()); + const wellHeadersQuery = useWellHeadersQuery(computedEnsembleIdent?.getCaseUuid()); // Handle Linked query useEffect(() => { if (parameterNamesQuery.data) { diff --git a/frontend/src/modules/Grid3D/view.tsx b/frontend/src/modules/Grid3D/view.tsx index 7f0487b62..a6fa599ec 100644 --- a/frontend/src/modules/Grid3D/view.tsx +++ b/frontend/src/modules/Grid3D/view.tsx @@ -9,7 +9,7 @@ import { createWellBoreHeaderLayer, createWellboreTrajectoryLayer, } from "@modules/SubsurfaceMap/_utils"; -import { useGetFieldWellsTrajectories } from "@modules/_shared/WellBore/queryHooks"; +import { useFieldWellsTrajectoriesQuery } from "@modules/_shared/WellBore/queryHooks"; import SubsurfaceViewer from "@webviz/subsurface-viewer"; import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; @@ -62,7 +62,7 @@ export function view({ moduleContext, workbenchSettings, workbenchSession }: Mod realizations, useStatistics ); - const wellTrajectoriesQuery = useGetFieldWellsTrajectories(firstCaseUuid ?? undefined); + const wellTrajectoriesQuery = useFieldWellsTrajectoriesQuery(firstCaseUuid ?? undefined); const bounds = gridSurfaceQuery?.data ? [ gridSurfaceQuery.data.xmin, diff --git a/frontend/src/modules/SeismicIntersection/loadModule.tsx b/frontend/src/modules/SeismicIntersection/loadModule.tsx index b1c03e691..5cf75709d 100644 --- a/frontend/src/modules/SeismicIntersection/loadModule.tsx +++ b/frontend/src/modules/SeismicIntersection/loadModule.tsx @@ -5,7 +5,7 @@ import { State } from "./state"; import { view } from "./view"; const defaultState: State = { - wellboreAddress: { uwi: "55/33-A-4", uuid: "drogon_horizontal", type: "smda" }, + wellboreAddress: null, seismicAddress: null, extension: 1000, zScale: 5, diff --git a/frontend/src/modules/SeismicIntersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx index b04a94004..dad144f29 100644 --- a/frontend/src/modules/SeismicIntersection/queryHooks.tsx +++ b/frontend/src/modules/SeismicIntersection/queryHooks.tsx @@ -1,4 +1,4 @@ -import { Body_get_seismic_fence_api, SeismicCubeMeta_api, SeismicFencePolyline_api } from "@api"; +import { Body_post_get_seismic_fence_api, SeismicCubeMeta_api, SeismicFencePolyline_api } from "@api"; import { apiService } from "@framework/ApiService"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; @@ -7,13 +7,13 @@ import { SeismicFenceData_trans, transformSeismicFenceData } from "./utils/query const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; -export function useSeismicCubeDirectoryQuery( +export function useSeismicCubeMetaListQuery( caseUuid: string | undefined, ensembleName: string | undefined ): UseQueryResult { return useQuery({ - queryKey: ["getSeismicDirectory", caseUuid, ensembleName], - queryFn: () => apiService.seismic.getSeismicDirectory(caseUuid ?? "", ensembleName ?? ""), + queryKey: ["getSeismicCubeMetaList", caseUuid, ensembleName], + queryFn: () => apiService.seismic.getSeismicCubeMetaList(caseUuid ?? "", ensembleName ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: !!(caseUuid && ensembleName), @@ -30,10 +30,10 @@ export function useSeismicFenceDataQuery( polyline: SeismicFencePolyline_api | null, allowEnable: boolean ): UseQueryResult { - const bodyPolyline: Body_get_seismic_fence_api = { polyline: polyline ?? { x_points: [], y_points: [] } }; + const bodyPolyline: Body_post_get_seismic_fence_api = { polyline: polyline ?? { x_points: [], y_points: [] } }; return useQuery({ queryKey: [ - "getSeismicFence", + "postGetSeismicFence", caseUuid, ensembleName, realizationNum, @@ -43,7 +43,7 @@ export function useSeismicFenceDataQuery( bodyPolyline, ], queryFn: () => - apiService.seismic.getSeismicFence( + apiService.seismic.postGetSeismicFence( caseUuid ?? "", ensembleName ?? "", realizationNum ?? 0, diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx index 8614c7bda..662580c80 100644 --- a/frontend/src/modules/SeismicIntersection/settings.tsx +++ b/frontend/src/modules/SeismicIntersection/settings.tsx @@ -16,14 +16,14 @@ import { Label } from "@lib/components/Label"; import { RadioGroup } from "@lib/components/RadioGroup"; import { Select, SelectOption } from "@lib/components/Select"; import { useValidState } from "@lib/hooks/useValidState"; -import { useGetWellHeaders } from "@modules/_shared/WellBore"; +import { useWellHeadersQuery } from "@modules/_shared/WellBore"; import { isEqual } from "lodash"; -import { useSeismicCubeDirectoryQuery } from "./queryHooks"; +import { useSeismicCubeMetaListQuery } from "./queryHooks"; import { State } from "./state"; import { SeismicAddress } from "./types"; -import { SeismicCubeDirectory, TimeType } from "./utils/seismicCubeDirectory"; +import { SeismicCubeMetaDirectory, TimeType } from "./utils/seismicCubeDirectory"; const TimeTypeEnumToSurveyTypeStringMapping = { [TimeType.TimePoint]: "3D", @@ -38,6 +38,13 @@ const enum SeismicDataSource { OBSERVED = "Observed", } +// To be a variable in the future? +const WELLBORE_TYPE = "smda"; + +// +const EXTENSION_LIMITS = { min: 100, max: 100000 }; // Min/max extension in meters outside both sides of the well path [m] +const Z_SCALE_LIMITS = { min: 1, max: 100 }; // Minimum z-scale factor + export function settings({ moduleContext, workbenchSession, workbenchServices }: ModuleFCProps) { const syncedSettingKeys = moduleContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); @@ -45,8 +52,6 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: const ensembleSet = useEnsembleSet(workbenchSession); const statusWriter = useSettingsStatusWriter(moduleContext); - const wellboreType = "smda"; - const setSeismicAddress = moduleContext.useSetStoreValue("seismicAddress"); const setWellboreAddress = moduleContext.useSetStoreValue("wellboreAddress"); const [extension, setExtension] = moduleContext.useStoreState("extension"); @@ -68,23 +73,23 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: } // Queries - const wellHeadersQuery = useGetWellHeaders(computedEnsembleIdent?.getCaseUuid()); - const seismicCubeDirectoryQuery = useSeismicCubeDirectoryQuery( + const wellHeadersQuery = useWellHeadersQuery(computedEnsembleIdent?.getCaseUuid()); + const seismicCubeMetaListQuery = useSeismicCubeMetaListQuery( computedEnsembleIdent?.getCaseUuid(), computedEnsembleIdent?.getEnsembleName() ); if (wellHeadersQuery.isError) { statusWriter.addError("Error loading well headers"); } - if (seismicCubeDirectoryQuery.isError) { - statusWriter.addError("Error loading seismic directory"); + if (seismicCubeMetaListQuery.isError) { + statusWriter.addError("Error loading seismic cube meta list"); } // Handling well headers query const syncedWellBore = syncHelper.useValue(SyncSettingKey.WELLBORE, "global.syncValue.wellBore"); const availableWellboreList: Wellbore[] = wellHeadersQuery.data?.map((wellbore) => ({ - type: wellboreType, + type: WELLBORE_TYPE, uwi: wellbore.unique_wellbore_identifier, uuid: wellbore.wellbore_uuid, })) || []; @@ -98,10 +103,10 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: setSelectedWellboreAddress(computedWellboreAddress); } - // Handling seismic cube directory query - const seismicCubeDirectory = seismicCubeDirectoryQuery.data - ? new SeismicCubeDirectory({ - seismicCubeMetaArray: seismicCubeDirectoryQuery.data, + // Create seismic cube directory + const seismicCubeMetaDirectory = seismicCubeMetaListQuery.data + ? new SeismicCubeMetaDirectory({ + seismicCubeMetaList: seismicCubeMetaListQuery.data, timeType: surveyTimeType, useObservedSeismicCubes: isObserved, }) @@ -109,20 +114,20 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: const [selectedSeismicAttribute, setSelectedSeismicAttribute] = useValidState( null, - seismicCubeDirectory?.getAttributeNames() ?? [] + seismicCubeMetaDirectory?.getAttributeNames() ?? [] ); const [selectedTime, setSelectedTime] = useValidState( null, - seismicCubeDirectory?.getTimeOrIntervalStrings() ?? [] + seismicCubeMetaDirectory?.getTimeOrIntervalStrings() ?? [] ); - const seismicAttributeOptions = seismicCubeDirectory - ? seismicCubeDirectory.getAttributeNames().map((attribute) => { + const seismicAttributeOptions = seismicCubeMetaDirectory + ? seismicCubeMetaDirectory.getAttributeNames().map((attribute) => { return { label: attribute, value: attribute }; }) : []; - const timeOptions = seismicCubeDirectory - ? createOptionsFromTimeOrIntervalStrings(seismicCubeDirectory.getTimeOrIntervalStrings()) + const timeOptions = seismicCubeMetaDirectory + ? createOptionsFromTimeOrIntervalStrings(seismicCubeMetaDirectory.getTimeOrIntervalStrings()) : []; React.useEffect( @@ -199,22 +204,18 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: if (!wellUwi) return; - const newWellboreAddress: Wellbore = { type: wellboreType, uuid: wellboreUuid, uwi: wellUwi }; + const newWellboreAddress: Wellbore = { type: WELLBORE_TYPE, uuid: wellboreUuid, uwi: wellUwi }; setSelectedWellboreAddress(newWellboreAddress); syncHelper.publishValue(SyncSettingKey.WELLBORE, "global.syncValue.wellBore", newWellboreAddress); } function handleExtensionChange(event: React.ChangeEvent) { const newExtension = parseInt(event.target.value, 10); - if (newExtension >= 0) { - setExtension(newExtension); - } + setExtension(newExtension); } function handleZScaleChange(event: React.ChangeEvent) { const newZScale = parseInt(event.target.value, 10); - if (newZScale >= 0) { - setZScale(newZScale); - } + setZScale(newZScale); } return ( @@ -294,7 +295,7 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: /> } > @@ -321,10 +322,22 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }:
@@ -353,10 +366,35 @@ function createOptionsFromTimeOrIntervalStrings(timeOrIntervalStrings: string[]) if (timeOrIntervalStrings.length == 0) { return []; } - // '2018-01-01T00:00:00.000Z--2019-07-01T00:00:00.000Z' to '2018-01-01--2019-07-01' + + // '2018-01-01T00:00:00.000/2019-07-01T00:00:00.000' to '2018-01-01/2019-07-01' const options = timeOrIntervalStrings.map((elm) => { - const date = elm.replaceAll("T00:00:00.000Z", ""); - return { label: date, value: elm }; + const isInterval = elm.includes("/"); + return { value: elm, label: isInterval ? isoIntervalStringToDateLabel(elm) : isoStringToDateLabel(elm) }; }); return options; } + +/** + * Extracts the date substring from an ISO string + * + * Input ISO string format: '2018-01-01T00:00:00.000' + * Returns: '2018-01-01' + */ +function isoStringToDateLabel(inputIsoString: string): string { + const date = inputIsoString.split("T")[0]; + return `${date}`; +} + +/** + * Extracts interval date substring from an ISO string + * + * Input ISO string format: '2018-01-01T00:00:00.000/2019-07-01T00:00:00.000' + * Returns: '2018-01-01/2019-07-01' + */ +function isoIntervalStringToDateLabel(inputIsoIntervalString: string): string { + const [start, end] = inputIsoIntervalString.split("/"); + const startDate = start.split("T")[0]; + const endDate = end.split("T")[0]; + return `${startDate}/${endDate}`; +} diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts index 984686428..e4b0f6245 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionControllerUtils.ts @@ -1,5 +1,6 @@ import { Controller, + OverlayMouseExitEvent, OverlayMouseMoveEvent, SeismicCanvasLayer, WellborepathLayer, @@ -19,32 +20,36 @@ export function addMDOverlay(controller: Controller) { const newX = caller.currentStateAsEvent.xScale.invert(x); const referenceSystem = caller.referenceSystem; - if (!referenceSystem || !target) return; + if (!referenceSystem || !(target instanceof HTMLElement)) return; const md = referenceSystem.unproject(newX); target.textContent = Number.isFinite(md) ? `MD: ${md?.toFixed(1)}` : "-"; if (md && (md < 0 || referenceSystem.length < md)) { - target.setAttribute("style", "visibility: hidden"); + target.classList.replace("visible", "invisible"); } else { - target.setAttribute("style", "visibility: visible"); + target.classList.replace("invisible", "visible"); } }, - onMouseExit: (event: any) => { - event.target.style.visibility = "hidden"; + onMouseExit: (event: OverlayMouseExitEvent) => { + if (event.target instanceof HTMLElement) { + event.target.classList.replace("visible", "invisible"); + } }, }); + if (elm) { - elm.style.visibility = "hidden"; - elm.style.display = "inline-block"; - elm.style.padding = "2px"; - elm.style.borderRadius = "4px"; - elm.style.textAlign = "right"; - elm.style.position = "absolute"; - elm.style.backgroundColor = "rgba(0,0,0,0.5)"; - elm.style.color = "white"; - elm.style.right = "5px"; - elm.style.bottom = "5px"; - elm.style.zIndex = "100"; + elm.classList.add( + "invisible", + "inline-block", + "p-1", + "rounded", + "text-right", + "absolute", + "bg-black", + "bg-opacity-20", + "text-white", + "z-100" + ); } } diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts index b5731dec0..46fa9bfa6 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionDataConversion.ts @@ -151,7 +151,7 @@ export function makeReferenceSystemFromTrajectoryXyzPoints( * trace a,b,c and d * * num_traces = 4 - * num_samples = 3 + * num_samples_per_trace = 3 * fence_traces = [a1, a2, a3, b1, b2, b3, c1, c2, c3, d1, d2, d3] * * Image: @@ -167,7 +167,7 @@ export function createSeismicSliceImageDataArrayFromFenceData( const imageArray: number[][] = []; const numTraces = fenceData.num_traces; - const numSamples = fenceData.num_trace_samples; + const numSamples = fenceData.num_samples_per_trace; const fenceValues = fenceData.fenceTracesFloat32Arr; for (let i = 0; i < numSamples; ++i) { @@ -190,7 +190,7 @@ export function createSeismicSliceImageDataArrayFromFenceData( export function createSeismicSliceImageYAxisValuesArrayFromFenceData(fenceData: SeismicFenceData_trans): number[] { const yAxisValues: number[] = []; - const numSamples = fenceData.num_trace_samples; + const numSamples = fenceData.num_samples_per_trace; const minDepth = fenceData.min_fence_depth; const maxDepth = fenceData.max_fence_depth; diff --git a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts index c5206a547..5adc79d0e 100644 --- a/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts +++ b/frontend/src/modules/SeismicIntersection/utils/esvIntersectionHooks.ts @@ -18,21 +18,32 @@ export enum SeismicSliceImageStatus { ERROR = "error", } -export function useGenerateSeismicSliceImage(data: SeismicSliceImageOptions | null) { +export type SeismicSliceImageData = { + image: ImageBitmap | null; + synchedOptions: SeismicSliceImageOptions | null; + status: SeismicSliceImageStatus; +}; + +/** + * Hook to generate seismic slice image for async utility. + * + * Returns image, synched image options used to generate the image, and image status. + */ +export function useGenerateSeismicSliceImageData(imageOptions: SeismicSliceImageOptions | null): SeismicSliceImageData { const [prevData, setPrevData] = React.useState(null); const [image, setImage] = React.useState(null); const [imageStatus, setImageStatus] = React.useState(SeismicSliceImageStatus.SUCCESS); const [synchedImageOptions, setSynchedImageOptions] = React.useState(null); - if (data !== null && !isEqual(data, prevData)) { - setPrevData(data); + if (imageOptions !== null && !isEqual(imageOptions, prevData)) { + setPrevData(imageOptions); setImageStatus(SeismicSliceImageStatus.LOADING); // Async generation of seismic slice image generateSeismicSliceImage( - { datapoints: data.dataValues, yAxisValues: data.yAxisValues }, - data.trajectoryXyPoints, - data.colormap, + { datapoints: imageOptions.dataValues, yAxisValues: imageOptions.yAxisValues }, + imageOptions.trajectoryXyPoints, + imageOptions.colormap, { isLeftToRight: true, } @@ -40,14 +51,15 @@ export function useGenerateSeismicSliceImage(data: SeismicSliceImageOptions | nu .then((result) => { setImage(result ?? null); setImageStatus(SeismicSliceImageStatus.SUCCESS); - setSynchedImageOptions(data); + setSynchedImageOptions(imageOptions); }) .catch(() => { setImage(null); setImageStatus(SeismicSliceImageStatus.ERROR); - setSynchedImageOptions(data); + setSynchedImageOptions(imageOptions); }); } + // Slice image data return { image: image, synchedOptions: synchedImageOptions, status: imageStatus }; } diff --git a/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts b/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts index 01327299e..a41b1ef72 100644 --- a/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts +++ b/frontend/src/modules/SeismicIntersection/utils/seismicCubeDirectory.ts @@ -1,27 +1,27 @@ import { SeismicCubeMeta_api } from "@api"; import { isIsoStringInterval } from "@framework/utils/timestampUtils"; -// Time type for seismic directory. +// Time type for seismic cubes. export enum TimeType { TimePoint = "TimePoint", Interval = "Interval", } -export type SeismicCubeDirectoryOptions = { - seismicCubeMetaArray: SeismicCubeMeta_api[]; +export type SeismicCubeMetaDirectoryOptions = { + seismicCubeMetaList: SeismicCubeMeta_api[]; timeType: TimeType; useObservedSeismicCubes?: boolean; }; -// Class responsible for managing a directory of seismic cubes. -export class SeismicCubeDirectory { +// Class responsible for managing a list of seismic cube meta. +export class SeismicCubeMetaDirectory { private _seismicCubeList: SeismicCubeMeta_api[] = []; // Constructs a SeismicCubeDirectory with optional content filter criteria. - constructor(options: SeismicCubeDirectoryOptions) { + constructor(options: SeismicCubeMetaDirectoryOptions) { if (!options) return; - let filteredList = filterSeismicCubeMetaArrayOnTimeType(options.seismicCubeMetaArray, options.timeType); + let filteredList = filterSeismicCubeMetaListOnTimeType(options.seismicCubeMetaList, options.timeType); if (options.useObservedSeismicCubes) { filteredList = filteredList.filter((cube) => cube.is_observation); @@ -49,17 +49,17 @@ export class SeismicCubeDirectory { } // Internal utility to filter directory based on time type. -function filterSeismicCubeMetaArrayOnTimeType( - seismicCubeMetaArray: SeismicCubeMeta_api[], +function filterSeismicCubeMetaListOnTimeType( + seismicCubeMetaList: SeismicCubeMeta_api[], timeType: TimeType ): SeismicCubeMeta_api[] { switch (timeType) { case TimeType.TimePoint: - return seismicCubeMetaArray.filter( + return seismicCubeMetaList.filter( (cube) => cube.iso_date_or_interval && !isIsoStringInterval(cube.iso_date_or_interval) ); case TimeType.Interval: - return seismicCubeMetaArray.filter( + return seismicCubeMetaList.filter( (cube) => cube.iso_date_or_interval && isIsoStringInterval(cube.iso_date_or_interval) ); default: diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index 4362ae2e2..576e2ea1d 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -6,7 +6,7 @@ import { ModuleFCProps } from "@framework/Module"; import { useViewStatusWriter } from "@framework/StatusWriter"; import { useElementSize } from "@lib/hooks/useElementSize"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; -import { useGetWellTrajectories } from "@modules/_shared/WellBore/queryHooks"; +import { useWellTrajectoriesQuery } from "@modules/_shared/WellBore/queryHooks"; import { ContentError } from "@modules/_shared/components/ContentMessage"; import { isEqual } from "lodash"; @@ -24,7 +24,7 @@ import { import { SeismicSliceImageOptions, SeismicSliceImageStatus, - useGenerateSeismicSliceImage, + useGenerateSeismicSliceImageData, } from "./utils/esvIntersectionHooks"; export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { @@ -64,7 +64,7 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) // Async generating seismic slice image const [generateSeismicSliceImageOptions, setGenerateSeismicSliceImageOptions] = React.useState(null); - const generateSeismicSliceImageHook = useGenerateSeismicSliceImage(generateSeismicSliceImageOptions); + const generatedSeismicSliceImageData = useGenerateSeismicSliceImageData(generateSeismicSliceImageOptions); React.useEffect(function initializeEsvIntersectionController() { if (esvIntersectionContainerRef.current) { @@ -82,21 +82,20 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; } return () => { - console.debug("controller destroyed"); esvIntersectionControllerRef.current?.destroy(); }; }, []); // Get well trajectories query - const getWellTrajectoriesQuery = useGetWellTrajectories(wellboreAddress ? [wellboreAddress.uuid] : undefined); - if (getWellTrajectoriesQuery.isError) { + const wellTrajectoriesQuery = useWellTrajectoriesQuery(wellboreAddress ? [wellboreAddress.uuid] : undefined); + if (wellTrajectoriesQuery.isError) { statusWriter.addError("Error loading well trajectories"); } // Use first trajectory and create polyline for seismic fence query, and extended wellbore trajectory for generating seismic fence image let candidateSeismicFencePolyline = seismicFencePolyline; - if (getWellTrajectoriesQuery.data && getWellTrajectoriesQuery.data.length !== 0) { - const trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(getWellTrajectoriesQuery.data[0]); + if (wellTrajectoriesQuery.data && wellTrajectoriesQuery.data.length !== 0) { + const trajectoryXyzPoints = makeTrajectoryXyzPointsFromWellboreTrajectory(wellTrajectoriesQuery.data[0]); const newExtendedWellboreTrajectory = makeExtendedTrajectoryFromTrajectoryXyzPoints( trajectoryXyzPoints, extension @@ -175,37 +174,37 @@ export const view = ({ moduleContext, workbenchSettings }: ModuleFCProps) if ( generateSeismicSliceImageOptions && - generateSeismicSliceImageHook.synchedOptions && - generateSeismicSliceImageHook.image && - generateSeismicSliceImageHook.status === SeismicSliceImageStatus.SUCCESS + generatedSeismicSliceImageData.synchedOptions && + generatedSeismicSliceImageData.image && + generatedSeismicSliceImageData.status === SeismicSliceImageStatus.SUCCESS ) { addSeismicLayer(esvIntersectionControllerRef.current, { - curtain: generateSeismicSliceImageHook.synchedOptions.trajectoryXyPoints, - xAxisOffset: generateSeismicSliceImageHook.synchedOptions.extension, - image: generateSeismicSliceImageHook.image, - dataValues: generateSeismicSliceImageHook.synchedOptions.dataValues, - yAxisValues: generateSeismicSliceImageHook.synchedOptions.yAxisValues, + curtain: generatedSeismicSliceImageData.synchedOptions.trajectoryXyPoints, + xAxisOffset: generatedSeismicSliceImageData.synchedOptions.extension, + image: generatedSeismicSliceImageData.image, + dataValues: generatedSeismicSliceImageData.synchedOptions.dataValues, + yAxisValues: generatedSeismicSliceImageData.synchedOptions.yAxisValues, }); } // Update layout - esvIntersectionControllerRef.current.zoomPanHandler.zFactor = zScale; + esvIntersectionControllerRef.current.zoomPanHandler.zFactor = Math.max(1.0, zScale); // Prevent scaling to zero esvIntersectionControllerRef.current.adjustToSize( Math.max(0, wrapperDivSize.width), Math.max(0, wrapperDivSize.height - 100) ); } - statusWriter.setLoading(getWellTrajectoriesQuery.isFetching || seismicFenceDataQuery.isFetching); + statusWriter.setLoading(wellTrajectoriesQuery.isFetching || seismicFenceDataQuery.isFetching); return (
- {seismicFenceDataQuery.isError && getWellTrajectoriesQuery.isError ? ( + {seismicFenceDataQuery.isError && wellTrajectoriesQuery.isError ? ( Error loading well trajectories and seismic fence data ) : seismicFenceDataQuery.isError ? ( Error loading seismic fence data - ) : getWellTrajectoriesQuery.isError ? ( + ) : wellTrajectoriesQuery.isError ? ( Error loading well trajectories - ) : generateSeismicSliceImageHook.status === SeismicSliceImageStatus.ERROR ? ( + ) : generatedSeismicSliceImageData.status === SeismicSliceImageStatus.ERROR ? ( Error generating seismic slice image ) : (
diff --git a/frontend/src/modules/SubsurfaceMap/settings.tsx b/frontend/src/modules/SubsurfaceMap/settings.tsx index ebc22a2f4..622c58895 100644 --- a/frontend/src/modules/SubsurfaceMap/settings.tsx +++ b/frontend/src/modules/SubsurfaceMap/settings.tsx @@ -23,7 +23,7 @@ import { TimeType, useSurfaceDirectoryQuery, } from "@modules/_shared/Surface"; -import { useGetWellHeaders } from "@modules/_shared/WellBore/queryHooks"; +import { useWellHeadersQuery } from "@modules/_shared/WellBore/queryHooks"; import { SurfacePolygonsAddress } from "./SurfacePolygonsAddress"; import { AggregationSelector } from "./components/AggregationSelector"; @@ -337,7 +337,7 @@ export function settings({ moduleContext, workbenchSession, workbenchServices }: [show3D] ); - const wellHeadersQuery = useGetWellHeaders(computedEnsembleIdent?.getCaseUuid()); + const wellHeadersQuery = useWellHeadersQuery(computedEnsembleIdent?.getCaseUuid()); let wellHeaderOptions: SelectOption[] = []; if (wellHeadersQuery.data) { diff --git a/frontend/src/modules/SubsurfaceMap/view.tsx b/frontend/src/modules/SubsurfaceMap/view.tsx index c74b44cfe..f9e4c1815 100644 --- a/frontend/src/modules/SubsurfaceMap/view.tsx +++ b/frontend/src/modules/SubsurfaceMap/view.tsx @@ -8,11 +8,10 @@ import { Wellbore } from "@framework/Wellbore"; import { Button } from "@lib/components/Button"; import { CircularProgress } from "@lib/components/CircularProgress"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; -import { useGetFieldWellsTrajectories } from "@modules/_shared/WellBore/queryHooks"; +import { useFieldWellsTrajectoriesQuery } from "@modules/_shared/WellBore/queryHooks"; import { useSurfaceDataQueryByAddress } from "@modules_shared/Surface"; import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; -import { usePolygonsDataQueryByAddress, usePropertySurfaceDataByQueryAddress } from "././queryHooks"; import { SurfaceMeta, createAxesLayer, @@ -24,6 +23,7 @@ import { createWellboreTrajectoryLayer, } from "./_utils"; import { SyncedSubsurfaceViewer } from "./components/SyncedSubsurfaceViewer"; +import { usePolygonsDataQueryByAddress, usePropertySurfaceDataByQueryAddress } from "./queryHooks"; import { state } from "./state"; type Bounds = [number, number, number, number]; @@ -87,7 +87,7 @@ export function view({ moduleContext, workbenchSettings, workbenchServices }: Mo const hasMeshSurfData = meshSurfDataQuery?.data ? true : false; const propertySurfDataQuery = usePropertySurfaceDataByQueryAddress(meshSurfAddr, propertySurfAddr, hasMeshSurfData); - const wellTrajectoriesQuery = useGetFieldWellsTrajectories(meshSurfAddr?.caseUuid); + const wellTrajectoriesQuery = useFieldWellsTrajectoriesQuery(meshSurfAddr?.caseUuid); const polygonsQuery = usePolygonsDataQueryByAddress(polygonsAddr); const newLayers: Record[] = [createNorthArrowLayer()]; diff --git a/frontend/src/modules/_shared/WellBore/index.ts b/frontend/src/modules/_shared/WellBore/index.ts index 5c65d542d..31e808339 100644 --- a/frontend/src/modules/_shared/WellBore/index.ts +++ b/frontend/src/modules/_shared/WellBore/index.ts @@ -1 +1 @@ -export { useGetFieldWellsTrajectories, useGetWellHeaders } from "./queryHooks"; +export { useFieldWellsTrajectoriesQuery, useWellHeadersQuery, useWellTrajectoriesQuery } from "./queryHooks"; diff --git a/frontend/src/modules/_shared/WellBore/queryHooks.ts b/frontend/src/modules/_shared/WellBore/queryHooks.ts index 453092476..4c77f4e99 100644 --- a/frontend/src/modules/_shared/WellBore/queryHooks.ts +++ b/frontend/src/modules/_shared/WellBore/queryHooks.ts @@ -5,7 +5,7 @@ import { UseQueryResult, useQuery } from "@tanstack/react-query"; const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; -export function useGetWellHeaders(caseUuid: string | undefined): UseQueryResult { +export function useWellHeadersQuery(caseUuid: string | undefined): UseQueryResult { return useQuery({ queryKey: ["getWellHeaders", caseUuid], queryFn: () => apiService.well.getWellHeaders(caseUuid ?? ""), @@ -15,7 +15,7 @@ export function useGetWellHeaders(caseUuid: string | undefined): UseQueryResult< }); } -export function useGetFieldWellsTrajectories(caseUuid: string | undefined): UseQueryResult { +export function useFieldWellsTrajectoriesQuery(caseUuid: string | undefined): UseQueryResult { return useQuery({ queryKey: ["getFieldWellsTrajectories", caseUuid], queryFn: () => apiService.well.getFieldWellTrajectories(caseUuid ?? ""), @@ -25,7 +25,7 @@ export function useGetFieldWellsTrajectories(caseUuid: string | undefined): UseQ }); } -export function useGetWellTrajectories(wellUuids: string[] | undefined): UseQueryResult { +export function useWellTrajectoriesQuery(wellUuids: string[] | undefined): UseQueryResult { return useQuery({ queryKey: ["getWellTrajectories", wellUuids], queryFn: () => apiService.well.getWellTrajectories(wellUuids ?? []), From 7fb9d9899bdf1f1c2b81dd8c419c72ff879cc6be Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 30 Nov 2023 15:32:30 +0100 Subject: [PATCH 34/35] Corrections after merge --- .github/workflows/webviz.yml | 2 +- backend/poetry.lock | 2 +- frontend/package-lock.json | 90 ++++++++------------- frontend/src/modules/SubsurfaceMap/view.tsx | 3 +- 4 files changed, 35 insertions(+), 62 deletions(-) diff --git a/.github/workflows/webviz.yml b/.github/workflows/webviz.yml index d3b6994e9..90af5f631 100644 --- a/.github/workflows/webviz.yml +++ b/.github/workflows/webviz.yml @@ -71,7 +71,7 @@ jobs: - name: 🕵️ Check auto-generated frontend code is in sync with backend run: | docker build -f backend.Dockerfile -t backend:latest . - CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env UVICORN_ENTRYPOINT=src.backend.primary.main:app --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 backend:latest) + CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env UVICORN_ENTRYPOINT=src.backend.primary.main:app --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 --env WEBVIZ_VDS_HOST_ADDRESS=0 backend:latest) sleep 5 # Ensure the backend server is up and running exposing /openapi.json npm run generate-api --prefix ./frontend docker stop $CONTAINER_ID diff --git a/backend/poetry.lock b/backend/poetry.lock index 993a8584d..49aa0e3a3 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -3393,4 +3393,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "c63aa3cc79c32d54136c1713b462e93682ee01a5f2dc49e2c1b1b104ae0ba0f8" +content-hash = "01abfe1c92e73df580ac7d7a6bddc91a7960b1b8f14b65042db25d4da8488c4b" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index da41d3128..33ef8dd06 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2958,13 +2958,13 @@ } }, "node_modules/@playwright/experimental-ct-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", - "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.40.1.tgz", + "integrity": "sha512-FjYQP74I2xVAO6W52+Yn2t48FDs+IhOHcWAwZHqAX+lHVHidz4f/b0FY3Qnq8+ZIt5TgqucEzGXbw/TdqJYDtA==", "dev": true, "dependencies": { - "playwright": "1.39.0", - "playwright-core": "1.39.0", + "playwright": "1.40.1", + "playwright-core": "1.40.1", "vite": "^4.4.10" }, "bin": { @@ -2975,12 +2975,12 @@ } }, "node_modules/@playwright/experimental-ct-react": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz", - "integrity": "sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.40.1.tgz", + "integrity": "sha512-a2ubB04+pSswpWOgIwgBcSvvdvVNv4Cz8wud5ZLV5+4fcRqRACxFlGJPiVHw1zanhDSD+rH6H9+zaNm/o1iJHw==", "dev": true, "dependencies": { - "@playwright/experimental-ct-core": "1.39.0", + "@playwright/experimental-ct-core": "1.40.1", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -2991,12 +2991,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", "dev": true, "dependencies": { - "playwright": "1.39.0" + "playwright": "1.40.1" }, "bin": { "playwright": "cli.js" @@ -3873,16 +3873,10 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", - "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", - "peer": true - }, "node_modules/@types/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==", + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", "dev": true }, "node_modules/@types/chai-subset": { @@ -3894,6 +3888,12 @@ "@types/chai": "*" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "peer": true + }, "node_modules/@types/culori": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/culori/-/culori-2.0.0.tgz", @@ -3906,12 +3906,6 @@ "integrity": "sha512-EU6fwVNP1TGVTkCILfURtzzwJq/ie5LgipELnzCINgm4VdDIkkbB8wnLSe81J77Bbqf4MiO3sJGhWzc6MCp5dQ==", "peer": true }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", @@ -3979,9 +3973,9 @@ "dev": true }, "node_modules/@types/lodash-es": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.11.tgz", - "integrity": "sha512-eCw8FYAWHt2DDl77s+AMLLzPn310LKohruumpucZI4oOFJkIgnlaJcy23OKMJxx4r9PeTF13Gv6w+jqjWQaYUg==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "dev": true, "dependencies": { "@types/lodash": "*" @@ -10853,10 +10847,6 @@ "url": "https://opencollective.com/pixijs" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -10869,12 +10859,12 @@ } }, "node_modules/playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", "dev": true, "dependencies": { - "playwright-core": "1.39.0" + "playwright-core": "1.40.1" }, "bin": { "playwright": "cli.js" @@ -10887,9 +10877,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -11319,22 +11309,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] - }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", diff --git a/frontend/src/modules/SubsurfaceMap/view.tsx b/frontend/src/modules/SubsurfaceMap/view.tsx index 99fd2f787..239552165 100644 --- a/frontend/src/modules/SubsurfaceMap/view.tsx +++ b/frontend/src/modules/SubsurfaceMap/view.tsx @@ -13,7 +13,6 @@ import { useFieldWellsTrajectoriesQuery } from "@modules/_shared/WellBore/queryH import { useSurfaceDataQueryByAddress } from "@modules_shared/Surface"; import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; -import { usePropertySurfaceDataByQueryAddress } from "./queryHooks"; import { SurfaceMeta, createAxesLayer, @@ -25,7 +24,7 @@ import { createWellboreTrajectoryLayer, } from "./_utils"; import { SyncedSubsurfaceViewer } from "./components/SyncedSubsurfaceViewer"; -import { usePolygonsDataQueryByAddress, usePropertySurfaceDataByQueryAddress } from "./queryHooks"; +import { usePropertySurfaceDataByQueryAddress } from "./queryHooks"; import { state } from "./state"; type Bounds = [number, number, number, number]; From 292dfff49e63e71b07f2feaaa993952000a58512 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Fri, 1 Dec 2023 08:36:13 +0100 Subject: [PATCH 35/35] Fix comment --- frontend/src/modules/SeismicIntersection/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings.tsx index 662580c80..792ddcd4d 100644 --- a/frontend/src/modules/SeismicIntersection/settings.tsx +++ b/frontend/src/modules/SeismicIntersection/settings.tsx @@ -41,7 +41,7 @@ const enum SeismicDataSource { // To be a variable in the future? const WELLBORE_TYPE = "smda"; -// +// Hardcoded min/max limits for input elements const EXTENSION_LIMITS = { min: 100, max: 100000 }; // Min/max extension in meters outside both sides of the well path [m] const Z_SCALE_LIMITS = { min: 1, max: 100 }; // Minimum z-scale factor