diff --git a/setup.py b/setup.py index ded828280..8ac4cb9e6 100644 --- a/setup.py +++ b/setup.py @@ -67,14 +67,13 @@ "defusedxml>=0.6.0", "ecl2df>=0.6.1; sys_platform=='linux'", "fmu-ensemble>=1.2.3", - "matplotlib>=3.0", "opm>=2020.10.1; sys_platform=='linux'", "pandas>=0.24", "pillow>=6.1", "pyscal>=0.4.1", "scipy>=1.2", "webviz-config>=0.0.55", - "webviz-subsurface-components>=0.0.27", + "webviz-subsurface-components>=0.2.0", "xtgeo>=2.8", ], tests_require=TESTS_REQUIRE, diff --git a/tests/unit_tests/data_input/test_image_processing.py b/tests/unit_tests/data_input/test_image_processing.py index b2324a1a4..e813707c4 100644 --- a/tests/unit_tests/data_input/test_image_processing.py +++ b/tests/unit_tests/data_input/test_image_processing.py @@ -1,20 +1,11 @@ import numpy as np -from webviz_subsurface._datainput.image_processing import ( - array_to_png, - get_colormap, -) +from webviz_subsurface._datainput.image_processing import array_to_png with open("tests/data/surface_png.txt", "r") as f: BASE64_SURFACE = f.read() -with open("tests/data/colormap.txt", "r") as f: - BASE64_COLORMAP = f.read() def test_array_to_png(): data = np.loadtxt("tests/data/surface_zarr.np.gz") assert array_to_png(data) == BASE64_SURFACE - - -def test_colormap(): - assert get_colormap("viridis") == BASE64_COLORMAP diff --git a/webviz_subsurface/_datainput/image_processing.py b/webviz_subsurface/_datainput/image_processing.py index a38841228..277022c5a 100644 --- a/webviz_subsurface/_datainput/image_processing.py +++ b/webviz_subsurface/_datainput/image_processing.py @@ -2,7 +2,6 @@ import base64 import numpy as np -from matplotlib import cm from PIL import Image @@ -76,9 +75,3 @@ def array_to_png( base64_data = base64.b64encode(byte_io.read()).decode("ascii") return f"data:image/png;base64,{base64_data}" - - -def get_colormap(colormap): - return array_to_png( - cm.get_cmap(colormap, 256)([np.linspace(0, 1, 256)]), colormap=True - ) diff --git a/webviz_subsurface/_datainput/surface.py b/webviz_subsurface/_datainput/surface.py index 0a664dace..84834b7d6 100644 --- a/webviz_subsurface/_datainput/surface.py +++ b/webviz_subsurface/_datainput/surface.py @@ -5,7 +5,7 @@ from webviz_config.common_cache import CACHE from PIL import Image -from .image_processing import array_to_png, get_colormap +from .image_processing import array_to_png @CACHE.memoize(timeout=CACHE.TIMEOUT) @@ -33,43 +33,11 @@ def get_surface_fence(fence, surface): def make_surface_layer( surface, name="surface", - min_val=None, - max_val=None, - color="viridis", - hillshading=False, - unit="", -): - """Make LayeredMap surface image base layer""" - zvalues = get_surface_arr(surface)[2] - bounds = [[surface.xmin, surface.ymin], [surface.xmax, surface.ymax]] - min_val = min_val if min_val is not None else np.nanmin(zvalues) - max_val = max_val if max_val is not None else np.nanmax(zvalues) - return { - "name": name, - "checked": True, - "base_layer": True, - "data": [ - { - "type": "image", - "url": array_to_png(zvalues.copy()), - "colormap": get_colormap(color), - "bounds": bounds, - "allowHillshading": hillshading, - "minvalue": f"{min_val:.2f}" if min_val is not None else None, - "maxvalue": f"{max_val:.2f}" if max_val is not None else None, - "unit": str(unit), - } - ], - } - - -def new_make_surface_layer( - surface, - name="surface", + updatemode="update", min_val=None, max_val=None, color=None, - shader_type="hillshading", + shader_type="soft-hillshading", unit="", ): """Make NewLayeredMap surface image base layer @@ -84,12 +52,11 @@ def new_make_surface_layer( Returns: A surface layer that can be plotted in NewLayeredMap """ + zvalues = get_surface_arr(surface)[2] - bounds = [[surface.xmin, surface.ymin], [surface.xmax, surface.ymax]] min_val = min_val if min_val is not None else np.nanmin(zvalues) max_val = max_val if max_val is not None else np.nanmax(zvalues) - image = base64.b64decode(array_to_png(zvalues.copy())[22:]) - img = Image.open(io.BytesIO(image)) + img = Image.open(io.BytesIO(base64.b64decode(array_to_png(zvalues.copy())[22:]))) width, height = img.size if width * height >= 300 * 300: scale = 1.0 @@ -99,6 +66,8 @@ def new_make_surface_layer( return { "name": name, "checked": True, + "id": name, + "action": updatemode, "baseLayer": True, "data": [ { @@ -124,17 +93,17 @@ def new_make_surface_layer( "cutPointMin": min_val, "cutPointMax": max_val, }, - "bounds": bounds, "shader": { "type": shader_type, - "shadows": False, - "shadowIterations": 2, - "elevationScale": 0.05, + "shadows": True, + "shadowIterations": 128, + "elevationScale": 1.0, "pixelScale": 1000, "setBlackToAlpha": True, }, - "minvalue": min_val.round(2), - "maxvalue": max_val.round(2), + "bounds": [[surface.xmin, surface.ymin], [surface.xmax, surface.ymax]], + "minvalue": round(min_val, 4) if min_val else None, + "maxvalue": round(max_val, 4) if max_val else None, "unit": str(unit), "imageScale": scale, } @@ -165,7 +134,7 @@ def get_surface_layers(switch, surface_name, surfaces, min_val=None, max_val=Non layers = [] for i, surface in enumerate(surfaces): if surface is not None: - s_layer = new_make_surface_layer( + s_layer = make_surface_layer( surface, name=depth_list[i], min_val=min_val, diff --git a/webviz_subsurface/plugins/_surface_viewer_fmu.py b/webviz_subsurface/plugins/_surface_viewer_fmu.py index e565a216e..f45b9e2ad 100644 --- a/webviz_subsurface/plugins/_surface_viewer_fmu.py +++ b/webviz_subsurface/plugins/_surface_viewer_fmu.py @@ -9,7 +9,7 @@ from dash.exceptions import PreventUpdate import dash_html_components as html import dash_core_components as dcc -from webviz_subsurface_components import LayeredMap +from webviz_subsurface_components import LeafletMap import webviz_core_components as wcc from webviz_config.webviz_store import webvizstore from webviz_config.common_cache import CACHE @@ -349,39 +349,129 @@ def layout(self): style={"fontSize": "1rem"}, children=[ html.Div( - style={"margin": "10px", "flex": 4}, + style={"height": "600px", "margin": "10px", "flex": 4}, children=[ - LayeredMap( - sync_ids=[self.uuid("map2"), self.uuid("map3")], + LeafletMap( + syncedMaps=[self.uuid("map2"), self.uuid("map3")], id=self.uuid("map"), - height=600, layers=[], - hillShading=True, - ) + unitScale={}, + autoScaleMap=True, + minZoom=-5, + updateMode="update", + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomleft"}, + ), + html.Div( + children=[ + dcc.RadioItems( + id=self.uuid("hillshade"), + labelStyle={ + "display": "inline-block", + "text-align": "justify", + }, + options=[ + { + "value": None, + "label": "No hillshading", + }, + { + "value": "soft-hillshading", + "label": "Soft hillshading", + }, + { + "value": "hillshading", + "label": "Hillshading", + }, + ], + value=None, + ), + ] + ), ], ), html.Div( style={"margin": "10px", "flex": 4}, children=[ - LayeredMap( - sync_ids=[self.uuid("map"), self.uuid("map3")], + LeafletMap( + syncedMaps=[self.uuid("map"), self.uuid("map3")], id=self.uuid("map2"), - height=600, layers=[], - hillShading=True, - ) + unitScale={}, + autoScaleMap=True, + minZoom=-5, + updateMode="update", + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomright"}, + ), + html.Div( + children=[ + dcc.RadioItems( + id=self.uuid("hillshade2"), + labelStyle={ + "display": "inline-block", + "text-align": "justify", + }, + options=[ + { + "value": None, + "label": "No hillshading", + }, + { + "value": "soft-hillshading", + "label": "Soft hillshading", + }, + { + "value": "hillshading", + "label": "Hillshading", + }, + ], + value=None, + ), + ] + ), ], ), html.Div( style={"margin": "10px", "flex": 4}, children=[ - LayeredMap( - sync_ids=[self.uuid("map"), self.uuid("map2")], + LeafletMap( + syncedMaps=[self.uuid("map"), self.uuid("map2")], id=self.uuid("map3"), - height=600, layers=[], - hillShading=True, - ) + unitScale={}, + autoScaleMap=True, + minZoom=-5, + updateMode="update", + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomright"}, + ), + html.Div( + children=[ + dcc.RadioItems( + id=self.uuid("hillshade3"), + labelStyle={ + "display": "inline-block", + "text-align": "justify", + }, + options=[ + { + "value": None, + "label": "No hillshading", + }, + { + "value": "soft-hillshading", + "label": "Soft hillshading", + }, + { + "value": "hillshading", + "label": "Hillshading", + }, + ], + value=None, + ), + ] + ), ], ), dcc.Store( @@ -432,6 +522,9 @@ def set_callbacks(self, app): Input(self.uuid("attribute-settings"), "data"), Input(self.uuid("truncate-diff-min"), "value"), Input(self.uuid("truncate-diff-max"), "value"), + Input(self.uuid("hillshade"), "value"), + Input(self.uuid("hillshade2"), "value"), + Input(self.uuid("hillshade3"), "value"), ], ) # pylint: disable=too-many-arguments, too-many-locals @@ -446,6 +539,9 @@ def _set_base_layer( attribute_settings, diff_min, diff_max, + hillshade, + hillshade2, + hillshade3, ): if not data or not data2: raise PreventUpdate @@ -470,26 +566,22 @@ def _set_base_layer( make_surface_layer( surface, name="surface", - color=attribute_settings.get(data["attr"], {}).get( - "color", "viridis" - ), + color=attribute_settings.get(data["attr"], {}).get("color"), + shader_type=hillshade, min_val=attribute_settings.get(data["attr"], {}).get("min", None), max_val=attribute_settings.get(data["attr"], {}).get("max", None), unit=attribute_settings.get(data2["attr"], {}).get("unit", ""), - hillshading=True, ) ] surface_layers2 = [ make_surface_layer( surface2, - name="surface", - color=attribute_settings.get(data2["attr"], {}).get( - "color", "viridis" - ), + name="surface2", + color=attribute_settings.get(data2["attr"], {}).get("color"), + shader_type=hillshade2, min_val=attribute_settings.get(data2["attr"], {}).get("min", None), max_val=attribute_settings.get(data2["attr"], {}).get("max", None), unit=attribute_settings.get(data2["attr"], {}).get("unit", ""), - hillshading=True, ) ] @@ -503,11 +595,15 @@ def _set_base_layer( diff_layers.append( make_surface_layer( surface3, - name="surface", - color=attribute_settings.get(data["attr"], {}).get( - "color", "viridis" + name="surface3", + color=attribute_settings.get(data["attr"], {}).get("color"), + shader_type=hillshade3, + min_val=attribute_settings.get(data2["attr"], {}).get( + "min", None + ), + max_val=attribute_settings.get(data2["attr"], {}).get( + "max", None ), - hillshading=True, ) ) error_label = "" @@ -516,6 +612,7 @@ def _set_base_layer( error_label = ( "Cannot calculate because the surfaces have different geometries" ) + if self.well_layer: surface_layers.append(self.well_layer) surface_layers2.append(self.well_layer) diff --git a/webviz_subsurface/plugins/_surface_with_grid_cross_section.py b/webviz_subsurface/plugins/_surface_with_grid_cross_section.py index 0b0fc5bc0..0dccbc444 100644 --- a/webviz_subsurface/plugins/_surface_with_grid_cross_section.py +++ b/webviz_subsurface/plugins/_surface_with_grid_cross_section.py @@ -3,14 +3,13 @@ from typing import List import pandas as pd -from matplotlib.colors import ListedColormap import xtgeo from dash.exceptions import PreventUpdate from dash.dependencies import Input, Output, State import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc -from webviz_subsurface_components import LayeredMap +from webviz_subsurface_components import LeafletMap from webviz_config import WebvizPluginABC from webviz_config.webviz_store import webvizstore from webviz_config.utils import calculate_slider_step @@ -227,12 +226,48 @@ def surface_layout(self): "height": "800px", "zIndex": -9999, }, - children=LayeredMap( - id=self.ids("map-view"), - draw_toolbar_polyline=True, - hillShading=True, - layers=[], - ), + children=[ + LeafletMap( + id=self.ids("map-view"), + autoScaleMap=True, + minZoom=-5, + updateMode="update", + drawTools={ + "drawMarker": False, + "drawPolygon": False, + "drawPolyline": True, + "position": "topright", + }, + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomleft"}, + ), + html.Div( + children=[ + dcc.RadioItems( + id=self.uuid("hillshade"), + labelStyle={ + "display": "inline-block", + "text-align": "justify", + }, + options=[ + { + "value": None, + "label": "No hillshading", + }, + { + "value": "soft-hillshading", + "label": "Soft hillshading", + }, + { + "value": "hillshading", + "label": "Hillshading", + }, + ], + value=None, + ), + ] + ), + ], ) ] ), @@ -346,23 +381,27 @@ def set_callbacks(self, app): Input(self.ids("gridparameter"), "value"), Input(self.ids("color-values"), "value"), Input(self.ids("color-scale"), "colorscale"), + Input(self.uuid("hillshade"), "value"), ], ) def _render_surface( - surfacepath, surface_type, gridparameter, color_values, colorscale + surfacepath, + surface_type, + gridparameter, + color_values, + colorscale, + hillshade, ): surface = xtgeo.RegularSurface(get_path(surfacepath)) - hillshading = True min_val = None max_val = None - color = "viridis" + color = None if surface_type == "attribute": - hillshading = False min_val = color_values[0] if color_values else None max_val = color_values[1] if color_values else None - color = ListedColormap(colorscale) if colorscale else "viridis" + color = colorscale if colorscale else color grid = load_grid(get_path(self.gridfile)) gridparameter = load_grid_parameter(grid, get_path(gridparameter)) surface.slice_grid3d(grid, gridparameter) @@ -378,7 +417,7 @@ def _render_surface( min_val=min_val, max_val=max_val, color=color, - hillshading=hillshading, + shader_type=hillshade, ) return [s_layer] diff --git a/webviz_subsurface/plugins/_surface_with_seismic_cross_section.py b/webviz_subsurface/plugins/_surface_with_seismic_cross_section.py index e3a76dd7a..77866b8d8 100644 --- a/webviz_subsurface/plugins/_surface_with_seismic_cross_section.py +++ b/webviz_subsurface/plugins/_surface_with_seismic_cross_section.py @@ -3,14 +3,13 @@ from typing import List import pandas as pd -from matplotlib.colors import ListedColormap import xtgeo from dash.exceptions import PreventUpdate from dash.dependencies import Input, Output, State import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc -from webviz_subsurface_components import LayeredMap +from webviz_subsurface_components import LeafletMap from webviz_config import WebvizPluginABC from webviz_config.webviz_store import webvizstore from webviz_config.utils import calculate_slider_step @@ -221,12 +220,54 @@ def surface_layout(self): "height": "800px", "zIndex": -9999, }, - children=LayeredMap( - id=self.ids("map-view"), - draw_toolbar_polyline=True, - hillShading=True, - layers=[], - ), + children=[ + LeafletMap( + id=self.ids("map-view"), + autoScaleMap=True, + minZoom=-5, + updateMode="update", + # draw_toolbar_polyline=True, + layers=[ + # make_surface_layer( + # xtgeo.RegularSurface(), name="surface" + # ) + ], + drawTools={ + "drawMarker": False, + "drawPolygon": False, + "drawPolyline": True, + "position": "topright", + }, + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomleft"}, + ), + html.Div( + children=[ + dcc.RadioItems( + id=self.uuid("hillshade"), + labelStyle={ + "display": "inline-block", + "text-align": "justify", + }, + options=[ + { + "value": None, + "label": "No hillshading", + }, + { + "value": "soft-hillshading", + "label": "Soft hillshading", + }, + { + "value": "hillshading", + "label": "Hillshading", + }, + ], + value=None, + ), + ] + ), + ], ) ] ), @@ -338,28 +379,29 @@ def set_callbacks(self, app): Input(self.ids("cube"), "value"), Input(self.ids("color-values"), "value"), Input(self.ids("color-scale"), "colorscale"), + Input(self.uuid("hillshade"), "value"), ], ) def _render_surface( - surfacepath, surface_type, cubepath, color_values, colorscale + surfacepath, surface_type, cubepath, color_values, colorscale, hillshade ): surface = xtgeo.RegularSurface(get_path(surfacepath)) - hillshading = True min_val = None max_val = None - color = "viridis" + color = None if surface_type == "attribute": - hillshading = False min_val = color_values[0] if color_values else None max_val = color_values[1] if color_values else None - color = ListedColormap(colorscale) if colorscale else "viridis" + color = colorscale if colorscale else color cube = load_cube_data(get_path(cubepath)) surface.slice_cube(cube) surface.values = surface.values.filled(0) - surface.values[surface.values < min_val] = min_val - surface.values[surface.values > max_val] = max_val + if min_val is not None: + surface.values[surface.values < min_val] = min_val + if max_val is not None: + surface.values[surface.values > max_val] = max_val s_layer = make_surface_layer( surface, @@ -367,7 +409,7 @@ def _render_surface( min_val=min_val, max_val=max_val, color=color, - hillshading=hillshading, + shader_type=hillshade, ) return [s_layer] diff --git a/webviz_subsurface/plugins/_well_cross_section.py b/webviz_subsurface/plugins/_well_cross_section.py index 2ad03e5ac..b3e951000 100644 --- a/webviz_subsurface/plugins/_well_cross_section.py +++ b/webviz_subsurface/plugins/_well_cross_section.py @@ -7,7 +7,7 @@ import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc -from webviz_subsurface_components import LayeredMap +from webviz_subsurface_components import LeafletMap from webviz_config import WebvizPluginABC from webviz_config.webviz_store import webvizstore @@ -222,8 +222,15 @@ def layout(self): "zIndex": 10000, "visibility": "hidden", }, - children=LayeredMap( - height=400, id=self.ids("map"), layers=[] + children=LeafletMap( + id=self.ids("map"), + layers=[], + unitScale={}, + autoScaleMap=True, + minZoom=-5, + updateMode="update", + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomleft"}, ), ), wcc.Graph(id=self.ids("graph")), @@ -303,7 +310,7 @@ def _show_map(nclicks, style): return style, btn @app.callback( - [Output(self.ids("map"), "layers"), Output(self.ids("map"), "uirevision")], + Output(self.ids("map"), "layers"), [Input(self.ids("wells"), "value")], ) def _render_surface(wellfile): @@ -315,10 +322,9 @@ def _render_surface(wellfile): s_layer = make_surface_layer( surface, name=self.surfacenames[0], - hillshading=True, ) well_layer = make_well_layer(well, wellname) - return [s_layer, well_layer], "keep" + return [s_layer, well_layer] def add_webvizstore(self): return [ diff --git a/webviz_subsurface/plugins/_well_cross_section_fmu.py b/webviz_subsurface/plugins/_well_cross_section_fmu.py index d02c12ce6..db38389b5 100644 --- a/webviz_subsurface/plugins/_well_cross_section_fmu.py +++ b/webviz_subsurface/plugins/_well_cross_section_fmu.py @@ -12,7 +12,7 @@ import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc -from webviz_subsurface_components import LayeredMap +from webviz_subsurface_components import LeafletMap from webviz_config import WebvizPluginABC from webviz_config.webviz_store import webvizstore from webviz_config.common_cache import CACHE @@ -377,7 +377,16 @@ def layout(self): }, children=[ self.map_layout, - LayeredMap(height=400, id=self.ids("map"), layers=[]), + LeafletMap( + id=self.ids("map"), + layers=[], + unitScale={}, + autoScaleMap=True, + minZoom=-5, + updateMode="update", + mouseCoords={"position": "bottomright"}, + colorBar={"position": "bottomleft"}, + ), ], ), html.Button( @@ -491,7 +500,7 @@ def _show_map(nclicks, style): return style, "Show map" @app.callback( - [Output(self.ids("map"), "layers"), Output(self.ids("map"), "uirevision")], + Output(self.ids("map"), "layers"), [ Input(self.ids("fencespec"), "data"), Input(self.ids("surface-name"), "value"), @@ -503,6 +512,8 @@ def _render_surface(fencespec, surfacename, surfacetype, ensemble): """Update map""" intersect_layer = { "name": "Well", + "id": "Well", + "action": "replace", "checked": True, "base_layer": False, "data": [ @@ -521,10 +532,8 @@ def _render_surface(fencespec, surfacename, surfacetype, ensemble): self.surfacefolder, )[surfacetype] - surface_layer = make_surface_layer( - surface, name=surfacename, hillshading=True - ) - return [surface_layer, intersect_layer], "keep" + surface_layer = make_surface_layer(surface, name=surfacename) + return [surface_layer, intersect_layer] def add_webvizstore(self): store_functions = [