webviz-subsurface 0.2.33__py3-none-any.whl → 0.2.35__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. webviz_subsurface/_models/parameter_model.py +1 -0
  2. webviz_subsurface/_providers/ensemble_grid_provider/_xtgeo_to_vtk_explicit_structured_grid.py +1 -1
  3. webviz_subsurface/_providers/ensemble_grid_provider/grid_viz_service.py +6 -4
  4. webviz_subsurface/_providers/ensemble_summary_provider/_provider_impl_arrow_lazy.py +2 -2
  5. webviz_subsurface/_providers/ensemble_summary_provider/_provider_impl_arrow_presampled.py +1 -1
  6. webviz_subsurface/_providers/ensemble_summary_provider/_resampling.py +30 -22
  7. webviz_subsurface/_providers/ensemble_surface_provider/_provider_impl_file.py +1 -17
  8. webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py +1 -1
  9. webviz_subsurface/_utils/parameter_response.py +3 -1
  10. webviz_subsurface/plugins/_bhp_qc/views/_view_functions.py +1 -1
  11. webviz_subsurface/plugins/_co2_leakage/_utilities/plume_extent.py +1 -1
  12. webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +2 -2
  13. webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/view_elements/_vtk_view_3d_element.py +2 -2
  14. webviz_subsurface/plugins/_history_match.py +1 -1
  15. webviz_subsurface/plugins/_map_viewer_fmu/_tmp_well_pick_provider.py +3 -1
  16. webviz_subsurface/plugins/_map_viewer_fmu/_types.py +1 -0
  17. webviz_subsurface/plugins/_map_viewer_fmu/_utils.py +18 -1
  18. webviz_subsurface/plugins/_map_viewer_fmu/callbacks.py +38 -12
  19. webviz_subsurface/plugins/_map_viewer_fmu/layout.py +32 -18
  20. webviz_subsurface/plugins/_map_viewer_fmu/map_viewer_fmu.py +33 -5
  21. webviz_subsurface/plugins/_rft_plotter/_plugin.py +5 -0
  22. webviz_subsurface/plugins/_rft_plotter/_utils/_rft_plotter_data_model.py +5 -2
  23. webviz_subsurface/plugins/_running_time_analysis_fmu.py +3 -3
  24. webviz_subsurface/plugins/_segy_viewer.py +6 -26
  25. webviz_subsurface/plugins/_surface_with_grid_cross_section.py +3 -16
  26. webviz_subsurface/plugins/_surface_with_seismic_cross_section.py +3 -16
  27. webviz_subsurface/plugins/_volumetric_analysis/controllers/distribution_controllers.py +33 -29
  28. webviz_subsurface/plugins/_volumetric_analysis/controllers/selections_controllers.py +8 -12
  29. webviz_subsurface/plugins/_volumetric_analysis/views/main_view.py +0 -3
  30. webviz_subsurface/plugins/_volumetric_analysis/views/selections_view.py +2 -12
  31. webviz_subsurface/plugins/_volumetric_analysis/volumetric_analysis.py +45 -3
  32. webviz_subsurface/plugins/_well_log_viewer/well_log_viewer.py +1 -1
  33. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/METADATA +3 -4
  34. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/RECORD +39 -66
  35. tests/integration_tests/plugin_tests/__init__.py +0 -0
  36. tests/integration_tests/plugin_tests/test_bhp_qc.py +0 -12
  37. tests/integration_tests/plugin_tests/test_history_match.py +0 -18
  38. tests/integration_tests/plugin_tests/test_line_plotter_fmu.py +0 -29
  39. tests/integration_tests/plugin_tests/test_parameter_analysis.py +0 -26
  40. tests/integration_tests/plugin_tests/test_parameter_correlation.py +0 -25
  41. tests/integration_tests/plugin_tests/test_parameter_distribution.py +0 -13
  42. tests/integration_tests/plugin_tests/test_parameter_parallel_coordinates.py +0 -13
  43. tests/integration_tests/plugin_tests/test_parameter_response_correlation.py +0 -15
  44. tests/integration_tests/plugin_tests/test_property_statistics.py +0 -22
  45. tests/integration_tests/plugin_tests/test_pvt_plot.py +0 -15
  46. tests/integration_tests/plugin_tests/test_relative_permeability.py +0 -14
  47. tests/integration_tests/plugin_tests/test_reservoir_simulation_timeseries.py +0 -30
  48. tests/integration_tests/plugin_tests/test_reservoir_simulation_timeseries_onebyone.py +0 -16
  49. tests/integration_tests/plugin_tests/test_reservoir_simulation_timeseries_regional.py +0 -23
  50. tests/integration_tests/plugin_tests/test_rft_plotter.py +0 -43
  51. tests/integration_tests/plugin_tests/test_segy_viewer.py +0 -22
  52. tests/integration_tests/plugin_tests/test_simulation_timeseries_onebyone.py +0 -23
  53. tests/integration_tests/plugin_tests/test_structural_uncertainty.py +0 -244
  54. tests/integration_tests/plugin_tests/test_surface_viewer_fmu.py +0 -21
  55. tests/integration_tests/plugin_tests/test_surface_with_grid_crossection.py +0 -45
  56. tests/integration_tests/plugin_tests/test_surface_with_seismic_crossection.py +0 -35
  57. tests/integration_tests/plugin_tests/test_tornado_plotter_fmu.py +0 -14
  58. tests/integration_tests/plugin_tests/test_vfp_analysis.py +0 -13
  59. tests/integration_tests/plugin_tests/test_volumetric_analysis.py +0 -38
  60. tests/integration_tests/plugin_tests/test_well_log_viewer.py +0 -16
  61. webviz_subsurface/_providers/ensemble_summary_provider/dev_resampling_perf_testing.py +0 -112
  62. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/LICENSE +0 -0
  63. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/LICENSE.chromedriver +0 -0
  64. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/WHEEL +0 -0
  65. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/entry_points.txt +0 -0
  66. {webviz_subsurface-0.2.33.dist-info → webviz_subsurface-0.2.35.dist-info}/top_level.txt +0 -0
@@ -151,6 +151,7 @@ class ParametersModel:
151
151
  # if mix of gen_kw and sensitivity ensembles add
152
152
  # dummy sensitivvity columns to gen_kw ensembles
153
153
  gen_kw_mask = self._dataframe["SENSNAME"].isnull()
154
+ self._dataframe["SENSNAME"] = self._dataframe["SENSNAME"].astype(str)
154
155
  self._dataframe.loc[gen_kw_mask, "SENSNAME"] = "🎲"
155
156
  self._dataframe.loc[gen_kw_mask, "SENSCASE"] = "p10_p90"
156
157
 
@@ -75,7 +75,7 @@ def _create_vtk_esgrid_from_verts_and_conn(
75
75
  vtk_cell_array.SetData(8, conn_idarr)
76
76
 
77
77
  vtk_esgrid = vtkExplicitStructuredGrid()
78
- vtk_esgrid.SetDimensions(point_dims.tolist())
78
+ vtk_esgrid.SetDimensions(point_dims.tolist()) # type: ignore
79
79
  vtk_esgrid.SetPoints(vtk_points)
80
80
  vtk_esgrid.SetCells(vtk_cell_array)
81
81
 
@@ -365,15 +365,15 @@ class GridVizService:
365
365
 
366
366
  plane = vtkPlane()
367
367
  plane.SetOrigin([x_0, y_0, 0])
368
- plane.SetNormal(right_vec.tolist())
368
+ plane.SetNormal(right_vec.tolist()) # type: ignore
369
369
 
370
370
  plane_0 = vtkPlane()
371
371
  plane_0.SetOrigin([x_0, y_0, 0])
372
- plane_0.SetNormal(fwd_vec.tolist())
372
+ plane_0.SetNormal(fwd_vec.tolist()) # type: ignore
373
373
 
374
374
  plane_1 = vtkPlane()
375
375
  plane_1.SetOrigin([x_1, y_1, 0])
376
- plane_1.SetNormal((-fwd_vec).tolist())
376
+ plane_1.SetNormal((-fwd_vec).tolist()) # type: ignore
377
377
 
378
378
  cutter_alg.SetPlane(plane)
379
379
  cutter_alg.Update()
@@ -478,7 +478,9 @@ class GridVizService:
478
478
  i_ref = reference(0)
479
479
  j_ref = reference(0)
480
480
  k_ref = reference(0)
481
- grid.ComputeCellStructuredCoords(cell_id, i_ref, j_ref, k_ref, True) # type: ignore[arg-type]
481
+ grid.ComputeCellStructuredCoords(
482
+ cell_id, i_ref, j_ref, k_ref, True # type: ignore[arg-type]
483
+ )
482
484
 
483
485
  cell_property_val: Optional[np.ndarray] = None
484
486
  if property_spec:
@@ -313,7 +313,7 @@ class ProviderImplArrowLazy(EnsembleSummaryProvider):
313
313
  f"find_unique={et_find_unique_ms}ms)"
314
314
  )
315
315
 
316
- return intersected_dates.astype(datetime.datetime).tolist()
316
+ return intersected_dates.astype(datetime.datetime).tolist() # type: ignore
317
317
 
318
318
  def get_vectors_df(
319
319
  self,
@@ -377,7 +377,7 @@ class ProviderImplArrowLazy(EnsembleSummaryProvider):
377
377
  table = table.filter(real_mask)
378
378
  et_filter_ms = timer.lap_ms()
379
379
 
380
- np_lookup_date = np.datetime64(date, "ms")
380
+ np_lookup_date = np.datetime64(date).astype("M8[ms]")
381
381
  table = sample_segmented_multi_real_table_at_date(table, np_lookup_date)
382
382
 
383
383
  et_resample_ms = timer.lap_ms()
@@ -374,7 +374,7 @@ class ProviderImplArrowPresampled(EnsembleSummaryProvider):
374
374
  f"find_unique={et_find_unique_ms}ms)"
375
375
  )
376
376
 
377
- return intersected_dates.astype(datetime.datetime).tolist()
377
+ return intersected_dates.astype(datetime.datetime).tolist() # type: ignore
378
378
 
379
379
  def get_vectors_df(
380
380
  self,
@@ -17,7 +17,7 @@ def _truncate_day_to_monday(datetime_day: np.datetime64) -> np.datetime64:
17
17
  def _quarter_start_month(datetime_day: np.datetime64) -> np.datetime64:
18
18
  # A bit hackish, utilizes the fact that datetime64 is relative to epoch
19
19
  # 1970-01-01 which is the first day in Q1.
20
- datetime_month = np.datetime64(datetime_day, "M")
20
+ datetime_month = datetime_day.astype("M8[M]")
21
21
  return datetime_month - (datetime_month.astype(int) % 3)
22
22
 
23
23
 
@@ -30,44 +30,52 @@ def generate_normalized_sample_dates(
30
30
  """
31
31
 
32
32
  if freq == Frequency.DAILY:
33
- start = np.datetime64(min_date, "D")
34
- stop = np.datetime64(max_date, "D")
33
+ start = min_date.astype("M8[D]")
34
+ stop = max_date.astype("M8[D]")
35
35
  if stop < max_date:
36
- stop += 1
37
- sampledates = np.arange(start, stop + 1)
36
+ stop += np.timedelta64(1, "D")
37
+ sampledates = np.arange(start, stop + np.timedelta64(1, "D"))
38
+
38
39
  elif freq == Frequency.WEEKLY:
39
- start = _truncate_day_to_monday(np.datetime64(min_date, "D"))
40
- stop = _truncate_day_to_monday(np.datetime64(max_date, "D"))
40
+ start = _truncate_day_to_monday(min_date.astype("M8[D]"))
41
+ stop = _truncate_day_to_monday(max_date.astype("M8[D]"))
41
42
  if start > min_date:
42
- start -= 7
43
+ start -= np.timedelta64(7, "D")
43
44
  if stop < max_date:
44
- stop += 7
45
- sampledates = np.arange(start, stop + 1, 7)
45
+ stop += np.timedelta64(7, "D")
46
+ sampledates = np.arange(
47
+ start, stop + np.timedelta64(1, "D"), np.timedelta64(7, "D")
48
+ )
49
+
46
50
  elif freq == Frequency.MONTHLY:
47
- start = np.datetime64(min_date, "M")
48
- stop = np.datetime64(max_date, "M")
51
+ start = min_date.astype("M8[M]")
52
+ stop = max_date.astype("M8[M]")
49
53
  if stop < max_date:
50
- stop += 1
51
- sampledates = np.arange(start, stop + 1)
54
+ stop += np.timedelta64(1, "M")
55
+ sampledates = np.arange(start, stop + np.timedelta64(1, "M"))
56
+
52
57
  elif freq == Frequency.QUARTERLY:
53
58
  start = _quarter_start_month(min_date)
54
59
  stop = _quarter_start_month(max_date)
55
60
  if stop < max_date:
56
- stop += 3
57
- sampledates = np.arange(start, stop + 1, 3)
61
+ stop += np.timedelta64(3, "M")
62
+ sampledates = np.arange(
63
+ start, stop + np.timedelta64(1, "M"), np.timedelta64(3, "M")
64
+ )
65
+
58
66
  elif freq == Frequency.YEARLY:
59
- start = np.datetime64(min_date, "Y")
60
- stop = np.datetime64(max_date, "Y")
67
+ start = min_date.astype("M8[Y]")
68
+ stop = max_date.astype("M8[Y]")
61
69
  if stop < max_date:
62
- stop += 1
63
- sampledates = np.arange(start, stop + 1)
70
+ stop += np.timedelta64(1, "Y")
71
+ sampledates = np.arange(start, stop + np.timedelta64(1, "Y"))
72
+
64
73
  else:
65
74
  raise NotImplementedError(
66
75
  f"Currently not supporting resampling to frequency {freq}."
67
76
  )
68
77
 
69
- sampledates = sampledates.astype("datetime64[ms]")
70
-
78
+ sampledates = sampledates.astype("M8[ms]")
71
79
  return sampledates
72
80
 
73
81
 
@@ -11,7 +11,6 @@ import xtgeo
11
11
  from webviz_subsurface._utils.enum_shim import StrEnum
12
12
  from webviz_subsurface._utils.perf_timer import PerfTimer
13
13
 
14
- from ._stat_surf_cache import StatSurfCache
15
14
  from ._surface_discovery import SurfaceFileInfo
16
15
  from .ensemble_surface_provider import (
17
16
  EnsembleSurfaceProvider,
@@ -26,7 +25,6 @@ LOGGER = logging.getLogger(__name__)
26
25
 
27
26
  REL_SIM_DIR = "sim"
28
27
  REL_OBS_DIR = "obs"
29
- REL_STAT_CACHE_DIR = "stat_cache"
30
28
 
31
29
 
32
30
  # pylint: disable=too-few-public-methods
@@ -53,8 +51,6 @@ class ProviderImplFile(EnsembleSurfaceProvider):
53
51
  self._provider_dir = provider_dir
54
52
  self._inventory_df = surface_inventory_df
55
53
 
56
- self._stat_surf_cache = StatSurfCache(self._provider_dir / REL_STAT_CACHE_DIR)
57
-
58
54
  @staticmethod
59
55
  # pylint: disable=too-many-locals
60
56
  def write_backing_store(
@@ -237,22 +233,10 @@ class ProviderImplFile(EnsembleSurfaceProvider):
237
233
  ) -> Optional[xtgeo.RegularSurface]:
238
234
  timer = PerfTimer()
239
235
 
240
- surf = self._stat_surf_cache.fetch(address)
241
- if surf:
242
- LOGGER.debug(
243
- f"Fetched statistical surface from cache in: {timer.elapsed_s():.2f}s"
244
- )
245
- return surf
246
-
247
236
  surf = self._create_statistical_surface(address)
248
- et_create_s = timer.lap_s()
249
-
250
- self._stat_surf_cache.store(address, surf)
251
- et_write_cache_s = timer.lap_s()
252
237
 
253
238
  LOGGER.debug(
254
- f"Created and wrote statistical surface to cache in: {timer.elapsed_s():.2f}s ("
255
- f"create={et_create_s:.2f}s, store={et_write_cache_s:.2f}s), "
239
+ f"Created statistical surface in: {timer.elapsed_s():.2f}s ("
256
240
  f"[stat={address.statistic}, "
257
241
  f"attr={address.attribute}, name={address.name}, date={address.datestr}]"
258
242
  )
@@ -6,7 +6,7 @@ import xtgeo
6
6
 
7
7
  def surface_to_float32_array(surface: xtgeo.RegularSurface) -> io.BytesIO:
8
8
  values = surface.values.astype(np.float32)
9
- values.fill_value = np.NaN
9
+ values.fill_value = np.nan
10
10
  values = np.ma.filled(values)
11
11
 
12
12
  # Rotate 90 deg left.
@@ -37,7 +37,9 @@ def filter_and_sum_responses(
37
37
  if aggregation == "sum":
38
38
  return df.groupby("REAL").sum().reset_index()[["REAL", response]]
39
39
  if aggregation == "mean":
40
- return df.groupby("REAL").mean().reset_index()[["REAL", response]]
40
+ return (
41
+ df.groupby("REAL").mean(numeric_only=True).reset_index()[["REAL", response]]
42
+ )
41
43
  raise ValueError(f"Unknown aggregation '{aggregation}'.")
42
44
 
43
45
 
@@ -20,7 +20,7 @@ def filter_df(df: pd.DataFrame, ensemble: str, wells: List[str]) -> pd.DataFrame
20
20
  in statistics.
21
21
  """
22
22
  columns = ["ENSEMBLE"] + [f"WBHP:{well}" for well in wells]
23
- return df.loc[df["ENSEMBLE"] == ensemble][columns].replace(0, np.NaN)
23
+ return df.loc[df["ENSEMBLE"] == ensemble][columns].replace(0, np.nan)
24
24
 
25
25
 
26
26
  def calc_statistics(df: pd.DataFrame) -> pd.DataFrame:
@@ -105,4 +105,4 @@ def _find_contours(
105
105
 
106
106
  def _simplify(poly: np.ndarray, simplify_dist: float) -> List[List[float]]:
107
107
  simplified = shapely.geometry.LineString(poly).simplify(simplify_dist)
108
- return np.array(simplified.coords).tolist()
108
+ return np.array(simplified.coords).tolist() # type: ignore
@@ -6,7 +6,7 @@ from dash import html
6
6
  from dash.development.base_component import Component
7
7
  from webviz_config.utils import StrEnum
8
8
  from webviz_config.webviz_plugin_subclasses import ViewABC, ViewElementABC
9
- from webviz_subsurface_components import DashSubsurfaceViewer
9
+ from webviz_subsurface_components import SubsurfaceViewer
10
10
 
11
11
 
12
12
  class MainView(ViewABC):
@@ -48,7 +48,7 @@ class MapViewElement(ViewElementABC):
48
48
  children=[
49
49
  html.Div(
50
50
  [
51
- DashSubsurfaceViewer(
51
+ SubsurfaceViewer(
52
52
  id=self.register_component_unique_id(
53
53
  self.Ids.DECKGL_MAP
54
54
  ),
@@ -2,7 +2,7 @@ from dash import dcc, html
2
2
  from dash.development.base_component import Component
3
3
  from webviz_config.utils import StrEnum
4
4
  from webviz_config.webviz_plugin_subclasses import ViewElementABC
5
- from webviz_subsurface_components import DashSubsurfaceViewer
5
+ from webviz_subsurface_components import SubsurfaceViewer
6
6
 
7
7
 
8
8
  class VTKView3D(ViewElementABC):
@@ -45,7 +45,7 @@ class VTKView3D(ViewElementABC):
45
45
  html.Div(
46
46
  style={"position": "absolute", "width": "100%", "height": "90%"},
47
47
  children=[
48
- DashSubsurfaceViewer(
48
+ SubsurfaceViewer(
49
49
  id=self.register_component_unique_id(VTKView3D.Ids.VIEW),
50
50
  layers=[
51
51
  {
@@ -155,7 +155,7 @@ def _get_sorted_edges(number_observation_groups: int) -> Dict[str, list]:
155
155
  np.random.chisquare(df=1, size=number_observation_groups)
156
156
  )
157
157
 
158
- sorted_values = np.flip(sorted_values, 0)
158
+ sorted_values = np.flip(sorted_values, 0) # type: ignore
159
159
 
160
160
  p10 = np.percentile(sorted_values, 90, axis=1)
161
161
  p90 = np.percentile(sorted_values, 10, axis=1)
@@ -3,6 +3,7 @@ from typing import Dict, List, Optional
3
3
  import geojson
4
4
  import pandas as pd
5
5
 
6
+ from webviz_subsurface._utils.colors import hex_to_rgb
6
7
  from webviz_subsurface._utils.enum_shim import StrEnum
7
8
 
8
9
 
@@ -64,10 +65,11 @@ class WellPickProvider:
64
65
  point = geojson.Point(coordinates=coords, validate=validate_geometry)
65
66
 
66
67
  geocoll = geojson.GeometryCollection(geometries=[point])
67
-
68
68
  properties = {
69
69
  "name": row[WellPickTableColumns.WELL],
70
70
  "attribute": str(row[attribute]),
71
+ "point_color": hex_to_rgb(row.get("point_color", "#000")),
72
+ "text_color": hex_to_rgb(row.get("text_color", "#000")),
71
73
  }
72
74
 
73
75
  feature = geojson.Feature(
@@ -9,6 +9,7 @@ class LayerTypes(StrEnum):
9
9
  WELLTOPSLAYER = "GeoJsonLayer"
10
10
  DRAWING = "DrawingLayer"
11
11
  FAULTPOLYGONS = "FaultPolygonsLayer"
12
+ FIELD_OUTLINE = "GeoJsonLayer"
12
13
  GEOJSON = "GeoJsonLayer"
13
14
 
14
15
 
@@ -1,8 +1,10 @@
1
1
  import base64
2
2
  import io
3
3
  import math
4
- from typing import List
4
+ from typing import Dict, List
5
5
 
6
+ import geojson
7
+ import xtgeo
6
8
  from PIL import Image, ImageDraw
7
9
 
8
10
 
@@ -39,3 +41,18 @@ def create_colormap_image_string(
39
41
  draw.rectangle([(x_0, 0), (x_1, height)], fill=rgb_to_hex(color))
40
42
 
41
43
  return f"data:image/png;base64,{image_to_base64(img)}"
44
+
45
+
46
+ def xtgeo_polygons_to_geojson(polygons: xtgeo.Polygons) -> Dict:
47
+ feature_arr = []
48
+ for name, polygon in polygons.dataframe.groupby("POLY_ID"):
49
+ coords = [list(zip(polygon.X_UTME, polygon.Y_UTMN))]
50
+ feature = geojson.Feature(
51
+ geometry=geojson.Polygon(coords),
52
+ properties={
53
+ "name": f"id:{name}",
54
+ "color": [200, 200, 200],
55
+ },
56
+ )
57
+ feature_arr.append(feature)
58
+ return geojson.FeatureCollection(features=feature_arr)
@@ -9,6 +9,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
9
9
 
10
10
  import numpy as np
11
11
  import webviz_subsurface_components as wsc
12
+ import xtgeo
12
13
  from dash import ALL, MATCH, Input, Output, State, callback, callback_context, no_update
13
14
  from dash.exceptions import PreventUpdate
14
15
  from webviz_config import EncodedFile
@@ -32,7 +33,7 @@ from webviz_subsurface._providers import (
32
33
  from ._layer_model import DeckGLMapLayersModel
33
34
  from ._tmp_well_pick_provider import WellPickProvider
34
35
  from ._types import LayerTypes, SurfaceMode
35
- from ._utils import round_to_significant
36
+ from ._utils import round_to_significant, xtgeo_polygons_to_geojson
36
37
  from .layout import (
37
38
  DefaultSettings,
38
39
  LayoutElements,
@@ -51,6 +52,8 @@ def plugin_callbacks(
51
52
  surface_server: Union[SurfaceArrayServer, SurfaceImageServer],
52
53
  ensemble_fault_polygons_providers: Dict[str, EnsembleFaultPolygonsProvider],
53
54
  fault_polygons_server: FaultPolygonsServer,
55
+ field_outline_polygons: xtgeo.Polygons,
56
+ field_outline_color: Tuple[float, float, float],
54
57
  map_surface_names_to_fault_polygons: Dict[str, str],
55
58
  well_picks_provider: Optional[WellPickProvider],
56
59
  fault_polygon_attribute: Optional[str],
@@ -518,9 +521,27 @@ def plugin_callbacks(
518
521
  layer_data={
519
522
  "data": well_picks_provider.get_geojson(
520
523
  selected_wells, horizon_name
521
- )
524
+ ),
525
+ "getLineColor": "@@=properties.point_color",
526
+ "getFillColor": "@@=properties.point_color",
527
+ "getTextColor": "@@=properties.text_color",
528
+ },
529
+ )
530
+ if (
531
+ LayoutLabels.SHOW_FIELD_OUTLINE in options
532
+ and field_outline_polygons is not None
533
+ ):
534
+ layer_model.update_layer_by_id(
535
+ layer_id=f"{LayoutElements.FIELD_OUTLINE_LAYER}-{idx}",
536
+ layer_data={
537
+ "data": xtgeo_polygons_to_geojson(field_outline_polygons),
538
+ "filled": False,
539
+ "depthTest": False,
540
+ "lineWidthMinPixels": 2,
541
+ "getLineColor": field_outline_color,
522
542
  },
523
543
  )
544
+
524
545
  viewports = []
525
546
  view_annotations = []
526
547
  for idx, data in enumerate(surface_elements):
@@ -550,10 +571,13 @@ def plugin_callbacks(
550
571
  "show3D": False,
551
572
  "isSync": True,
552
573
  "layerIds": [
553
- f"{LayoutElements.MAP3D_LAYER}-{idx}"
554
- if isinstance(surface_server, SurfaceArrayServer)
555
- else f"{LayoutElements.COLORMAP_LAYER}-{idx}",
574
+ (
575
+ f"{LayoutElements.MAP3D_LAYER}-{idx}"
576
+ if isinstance(surface_server, SurfaceArrayServer)
577
+ else f"{LayoutElements.COLORMAP_LAYER}-{idx}"
578
+ ),
556
579
  f"{LayoutElements.FAULTPOLYGONS_LAYER}-{idx}",
580
+ f"{LayoutElements.FIELD_OUTLINE_LAYER}-{idx}",
557
581
  f"{LayoutElements.WELLS_LAYER}-{idx}",
558
582
  ],
559
583
  "name": make_viewport_label(data, tab_name, multi),
@@ -851,13 +875,15 @@ def plugin_callbacks(
851
875
  "colormap": {"value": colormap, "options": colormaps},
852
876
  "color_range": {
853
877
  "value": color_range,
854
- "step": calculate_slider_step(
855
- min_value=value_range[0],
856
- max_value=value_range[1],
857
- steps=100,
858
- )
859
- if value_range[0] != value_range[1]
860
- else 0,
878
+ "step": (
879
+ calculate_slider_step(
880
+ min_value=value_range[0],
881
+ max_value=value_range[1],
882
+ steps=100,
883
+ )
884
+ if value_range[0] != value_range[1]
885
+ else 0
886
+ ),
861
887
  "range": value_range,
862
888
  },
863
889
  }
@@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, Union
3
3
 
4
4
  import webviz_core_components as wcc
5
5
  from dash import dcc, html
6
- from webviz_subsurface_components import DashSubsurfaceViewer # type: ignore
6
+ from webviz_subsurface_components import SubsurfaceViewer # type: ignore
7
7
 
8
8
  from webviz_subsurface._utils.enum_shim import StrEnum
9
9
 
@@ -37,15 +37,17 @@ class LayoutElements(StrEnum):
37
37
  RANGE_RESET = "color-range-reset-button"
38
38
  RESET_BUTTOM_CLICK = "color-range-reset-stored-state"
39
39
  FAULTPOLYGONS = "fault-polygon-toggle"
40
+ FIELD_OUTLINE_TOGGLE = "field-outline-toggle"
40
41
  WRAPPER = "wrapper-for-selector-component"
41
42
  COLORWRAPPER = "wrapper-for-color-selector-component"
42
43
  OPTIONS = "options"
43
44
 
44
45
  COLORMAP_LAYER = "deckglcolormaplayer"
45
- HILLSHADING_LAYER = "deckglhillshadinglayer"
46
+
46
47
  WELLS_LAYER = "deckglwelllayer"
47
48
  MAP3D_LAYER = "deckglmap3dlayer"
48
49
  FAULTPOLYGONS_LAYER = "deckglfaultpolygonslayer"
50
+ FIELD_OUTLINE_LAYER = "deckglfieldoutlinelayer"
49
51
  REALIZATIONS_FILTER = "realization-filter-selector"
50
52
  OPTIONS_DIALOG = "options-dialog"
51
53
 
@@ -69,8 +71,8 @@ class LayoutLabels(StrEnum):
69
71
  LINK = "🔗 Link"
70
72
  FAULTPOLYGONS = "Fault polygons"
71
73
  SHOW_FAULTPOLYGONS = "Show fault polygons"
74
+ SHOW_FIELD_OUTLINE = "Show field outline"
72
75
  SHOW_WELLS = "Show wells"
73
- SHOW_HILLSHADING = "Hillshading"
74
76
  COMMON_SELECTIONS = "Options and global filters"
75
77
  REAL_FILTER = "Realization filter"
76
78
  WELL_FILTER = "Well filter"
@@ -183,7 +185,7 @@ def main_layout(
183
185
  realizations: List[int],
184
186
  color_tables: List[Dict],
185
187
  show_fault_polygons: bool = True,
186
- hillshading_enabled: bool = True,
188
+ show_field_outline: bool = False,
187
189
  render_surfaces_as_images: bool = True,
188
190
  ) -> html.Div:
189
191
  return html.Div(
@@ -240,9 +242,9 @@ def main_layout(
240
242
  DialogLayout(
241
243
  get_uuid,
242
244
  show_fault_polygons,
245
+ show_field_outline,
243
246
  well_names,
244
247
  realizations,
245
- hillshading_enabled,
246
248
  ),
247
249
  ]
248
250
  )
@@ -269,7 +271,7 @@ class MapViewLayout(FullScreen):
269
271
  ) -> None:
270
272
  super().__init__(
271
273
  children=html.Div(
272
- DashSubsurfaceViewer(
274
+ SubsurfaceViewer(
273
275
  id={"id": get_uuid(LayoutElements.DECKGLMAP), "tab": tab},
274
276
  layers=update_map_layers(1, render_surfaces_as_images),
275
277
  colorTables=color_tables,
@@ -304,18 +306,20 @@ class DialogLayout(wcc.Dialog):
304
306
  self,
305
307
  get_uuid: Callable,
306
308
  show_fault_polygons: bool,
309
+ show_field_outline: bool,
307
310
  well_names: List[str],
308
311
  realizations: List[int],
309
- hillshading_enabled: bool = True,
310
312
  ) -> None:
311
- checklist_options = [LayoutLabels.SHOW_HILLSHADING]
312
- checklist_values = (
313
- [LayoutLabels.SHOW_HILLSHADING] if hillshading_enabled else []
314
- )
313
+ checklist_options = []
314
+ checklist_values = []
315
315
  if show_fault_polygons:
316
316
  checklist_options.append(LayoutLabels.SHOW_FAULTPOLYGONS)
317
317
  checklist_values.append(LayoutLabels.SHOW_FAULTPOLYGONS)
318
318
 
319
+ if show_field_outline:
320
+ checklist_options.append(LayoutLabels.SHOW_FIELD_OUTLINE)
321
+ checklist_values.append(LayoutLabels.SHOW_FIELD_OUTLINE)
322
+
319
323
  if well_names:
320
324
  checklist_options.append(LayoutLabels.SHOW_WELLS)
321
325
  checklist_values.append(LayoutLabels.SHOW_FAULTPOLYGONS)
@@ -358,9 +362,11 @@ class LinkCheckBox(wcc.Checklist):
358
362
  clicked = selector in DefaultSettings.LINKED_SELECTORS.get(tab, [])
359
363
  super().__init__(
360
364
  id={
361
- "id": get_uuid(LayoutElements.LINK)
362
- if selector not in ["color_range", "colormap"]
363
- else get_uuid(LayoutElements.COLORLINK),
365
+ "id": (
366
+ get_uuid(LayoutElements.LINK)
367
+ if selector not in ["color_range", "colormap"]
368
+ else get_uuid(LayoutElements.COLORLINK)
369
+ ),
364
370
  "tab": tab,
365
371
  "selector": selector,
366
372
  },
@@ -570,9 +576,11 @@ class MapSelectorLayout(html.Div):
570
576
  ) -> None:
571
577
  super().__init__(
572
578
  style={
573
- "display": "none"
574
- if tab == Tabs.STATS and selector == MapSelector.MODE
575
- else "block"
579
+ "display": (
580
+ "none"
581
+ if tab == Tabs.STATS and selector == MapSelector.MODE
582
+ else "block"
583
+ )
576
584
  },
577
585
  children=wcc.Selectors(
578
586
  label=label,
@@ -805,7 +813,13 @@ def update_map_layers(
805
813
  "parameters": {"depthTest": False},
806
814
  }
807
815
  )
808
-
816
+ layers.append(
817
+ {
818
+ "@@type": LayerTypes.FIELD_OUTLINE,
819
+ "id": f"{LayoutElements.FIELD_OUTLINE_LAYER}-{idx}",
820
+ "data": {"type": "FeatureCollection", "features": []},
821
+ }
822
+ )
809
823
  if include_well_layer:
810
824
  layers.append(
811
825
  {