From 501addf241da7b1294e57b97a54ab1c1f3d8cfb9 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Mon, 1 Aug 2022 16:00:05 +0200 Subject: [PATCH 1/7] saving --- webviz_subsurface/plugins/__init__.py | 2 +- .../__init__.py | 0 .../_business_logic.py | 0 .../_callbacks.py | 0 .../_figures.py | 0 .../_layout.py | 0 .../_markdown.py | 0 .../_plugin.py | 0 .../plugins/_swatint_qc/__init__.py | 1 + .../plugins/_swatint_qc/_error.py | 5 + .../plugins/_swatint_qc/_plugin.py | 42 +++++ .../plugins/_swatint_qc/_plugin_ids.py | 43 +++++ .../_swatint_qc/shared_settings/__init__.py | 1 + .../_swatint_qc/shared_settings/_filter.py | 57 ++++++ .../_swatint_qc/shared_settings/_pick_tab.py | 44 +++++ .../_swatint_qc/view_elements/__init__.py | 3 + .../_swatint_qc/view_elements/_dash_table.py | 20 +++ .../view_elements/_layout_style.py | 19 ++ .../_swatint_qc/view_elements/_map_figure.py | 78 ++++++++ .../view_elements/_properties_vs_depth.py | 166 ++++++++++++++++++ .../view_elements/_waterfall_plot.py | 118 +++++++++++++ .../overview_elements/__init__.py | 4 + .../overview_elements/_describtion.py | 48 +++++ .../overview_elements/_info_box.py | 48 +++++ .../overview_elements/_info_dialog.py | 50 ++++++ .../overview_elements/_overview_table.py | 38 ++++ .../plugins/_swatint_qc/views/__init__.py | 0 .../_swatint_qc/views/_capilary_preassure.py | 143 +++++++++++++++ .../_swatint_qc/views/_overview_info.py | 59 +++++++ .../views/_water_initialization.py | 166 ++++++++++++++++++ 30 files changed, 1154 insertions(+), 1 deletion(-) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/__init__.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_business_logic.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_callbacks.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_figures.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_layout.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_markdown.py (100%) rename webviz_subsurface/plugins/{_swatinit_qc => _swatinit_qc_old}/_plugin.py (100%) create mode 100644 webviz_subsurface/plugins/_swatint_qc/__init__.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/_error.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/_plugin.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/views/__init__.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py create mode 100644 webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py diff --git a/webviz_subsurface/plugins/__init__.py b/webviz_subsurface/plugins/__init__.py index 27a8548ac..f019ed49a 100644 --- a/webviz_subsurface/plugins/__init__.py +++ b/webviz_subsurface/plugins/__init__.py @@ -57,7 +57,7 @@ from ._surface_viewer_fmu import SurfaceViewerFMU from ._surface_with_grid_cross_section import SurfaceWithGridCrossSection from ._surface_with_seismic_cross_section import SurfaceWithSeismicCrossSection -from ._swatinit_qc import SwatinitQC +from ._swatinit_qc_old import SwatinitQC from ._tornado_plotter_fmu import TornadoPlotterFMU from ._volumetric_analysis import VolumetricAnalysis from ._well_analysis import WellAnalysis diff --git a/webviz_subsurface/plugins/_swatinit_qc/__init__.py b/webviz_subsurface/plugins/_swatinit_qc_old/__init__.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/__init__.py rename to webviz_subsurface/plugins/_swatinit_qc_old/__init__.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_business_logic.py b/webviz_subsurface/plugins/_swatinit_qc_old/_business_logic.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_business_logic.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_business_logic.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_callbacks.py b/webviz_subsurface/plugins/_swatinit_qc_old/_callbacks.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_callbacks.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_callbacks.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_figures.py b/webviz_subsurface/plugins/_swatinit_qc_old/_figures.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_figures.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_figures.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_layout.py b/webviz_subsurface/plugins/_swatinit_qc_old/_layout.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_layout.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_layout.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_markdown.py b/webviz_subsurface/plugins/_swatinit_qc_old/_markdown.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_markdown.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_markdown.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc_old/_plugin.py similarity index 100% rename from webviz_subsurface/plugins/_swatinit_qc/_plugin.py rename to webviz_subsurface/plugins/_swatinit_qc_old/_plugin.py diff --git a/webviz_subsurface/plugins/_swatint_qc/__init__.py b/webviz_subsurface/plugins/_swatint_qc/__init__.py new file mode 100644 index 000000000..262986762 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/__init__.py @@ -0,0 +1 @@ +from ._plugin import SwatinitQC diff --git a/webviz_subsurface/plugins/_swatint_qc/_error.py b/webviz_subsurface/plugins/_swatint_qc/_error.py new file mode 100644 index 000000000..bc998f235 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/_error.py @@ -0,0 +1,5 @@ +from dash import html + + +def error(error_message: str) -> html.Div: + return html.Div(children=error_message, style={"color": "red"}) diff --git a/webviz_subsurface/plugins/_swatint_qc/_plugin.py b/webviz_subsurface/plugins/_swatint_qc/_plugin.py new file mode 100644 index 000000000..80465e4d3 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/_plugin.py @@ -0,0 +1,42 @@ +from pathlib import Path +from typing import Callable, List, Optional, Tuple + +import webviz_core_components as wcc +from webviz_config import WebvizPluginABC, WebvizSettings + +from webviz_subsurface._datainput.fmu_input import find_sens_type +from webviz_subsurface._providers import EnsembleTableProviderFactory + +from ._error import error +from ._plugin_ids import PlugInIDs +from .shared_settings import PickTab + + +class SwatinitQC(WebvizPluginABC): + def __init__( + self, + webviz_settings: WebvizSettings, + csvfile: str = "share/results/tables/check_swatinit.csv", + ensemble: Optional[str] = None, + realization: Optional[int] = None, + faultlines: Path = None, + ) -> None: + super().__init__() + + self._datamodel = SwatinitQcDataModel( + webviz_settings=webviz_settings, + csvfile=csvfile, + ensemble=ensemble, + realization=realization, + faultlines=faultlines, + ) + + self.add_store(PlugInIDs.Stores.Shared.PICK_VIEW) + self.add_shared_settings_group(PickTab) + + self.add_store(PlugInIDs.Stores.Shared.EQLNUM) + self.add_shared_settings_group() + + @property + def layout(self) -> wcc.Tabs: + return plugin_main_layout(self.uuid, self._datamodel) diff --git a/webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py b/webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py new file mode 100644 index 000000000..915c5bcca --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py @@ -0,0 +1,43 @@ +class PlugInIDs: + # pylint: disable=too-few-public-methods + class Tabs: + # pylint: disable=too-few-public-methods + QC_PLOTS = "Water Initializaion QC plots" + MAX_PC_SCALING = "Capillary pressure scaling" + OVERVIEW = "Overview and Information" + + class Stores: + # pylint: disable=too-few-public-methods + class Shared: + # pylint: disable=too-few-public-methods + PICK_VIEW = "pick-view" + EQLNUM = "eqlnum" + + class Water: + # pylint: disable=too-few-public-methods + QC_VIZ = "qc-viz" + COLOR_BY = "color_by" + MAX_POINTS = "max-points" + QC_FLAG = "qc-flag" + STANUM = "satnum" + + class Capilary: + # pylint: disable=too-few-public-methods + SPLIT_TABLE_BY = "split-table-by" + MAX_PC_SCALE = "max-pc-scale" + + class SharedSettings: + # pylint: disable=too-few-public-methods + PICK_VIEW = "pick-view" + FILTERS = "filters" + + class QcFlags: + # pylint: disable=too-few-public-methods + FINE_EQUIL = "FINE_EQUIL" + HC_BELOW_FWL = "HC_BELOW_FWL" + PC_SCALED = "PC_SCALED" + PPCWMAX = "PPCWMAX" + SWATINIT_1 = "SWATINIT_1" + SWL_TRUNC = "SWL_TRUNC" + UNKNOWN = "UNKNOWN" + WATER = "WATER" diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py b/webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py new file mode 100644 index 000000000..e113e43c6 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py @@ -0,0 +1 @@ +from ._pick_tab import PickTab diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py b/webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py new file mode 100644 index 000000000..e6b2c894c --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py @@ -0,0 +1,57 @@ +from typing import List + +import webviz_core_components as wcc +from dash import Input, Output, callback, html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC + + +class Filters(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + EQLNUM = "eqlnum" + + def __init__( + self, + datamodel: SwatinitQcDataModel, + ) -> None: + super().__init__("Filters") + self.datamodel = datamodel + + @property + def layout(self) -> List[Component]: + elements = [ + wcc.SelectWithLabel( + label="EQLNUM", + id=self.register_component_unique_id(Filters.IDs.EQLNUM), + options=[ + {"label": ens, "value": ens} for ens in self.datamodel.eqlnums + ], + value=self.datamodel.eqlnums[:1], + size=min(8, len(self.datamodel.eqlnums)), + multi=True, + ), + ] + elements.append(self.range_filters) + return elements + + @property + def range_filters(uuid: str, datamodel: SwatinitQcDataModel) -> List: + dframe = datamodel.dframe + filters = [] + for col in datamodel.filters_continuous: + min_val, max_val = dframe[col].min(), dframe[col].max() + filters.append( + wcc.RangeSlider( + label="Depth range" if col == "Z" else col, + id={"id": uuid, "col": col}, + min=min_val, + max=max_val, + value=[min_val, max_val], + marks={ + str(val): {"label": f"{val:.2f}"} for val in [min_val, max_val] + }, + tooltip={"always_visible": False}, + ) + ) + return filters diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py b/webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py new file mode 100644 index 000000000..4ec7db27d --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py @@ -0,0 +1,44 @@ +from typing import List + +import webviz_core_components as wcc +from dash import Input, Output, callback, html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC + +from .._plugin_ids import PlugInIDs + + +class PickTab(SettingsGroupABC): + class IDs: + # pylint: diable=too-few-public-methods + TAB_PICKER = "tab-picker" + + def __init__(self) -> None: + super().__init__() + self.tab_options = [ + {"label": "Overview and Information", "value": "info"}, + {"label": "Water Initalization QC plots", "value": "water"}, + {"label": "Capillart preassure scaling", "value": "capilar"}, + ] + + def layout(self) -> Component: + return wcc.RadioItems( + id=self.register_component_unique_id(PickTab.IDs.TAB_PICKER), + options=self.tab_options, + value="info", + inline=False, + ) + + def set_callbacks(self) -> None: + @callback( + Output( + self.get_store_unique_id(PlugInIDs.Stores.Shared.PICK_VIEW), + "data", + ), + Input( + self.component_unique_id(PickTab.IDs.TAB_PICKER), + "value", + ), + ) + def _set_tab(tab: str) -> str: + return tab diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py new file mode 100644 index 000000000..4701c4e52 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py @@ -0,0 +1,3 @@ +from ._dash_table import DashTable +from ._layout_style import LayoutStyle +from .overview_elements import Describtion, InfoBox, InfoDialog, OverViewTable diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py new file mode 100644 index 000000000..ec6e8ba2d --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py @@ -0,0 +1,20 @@ +from typing import Any, List + +from dash import dash_table + +from ._layout_style import LayoutStyle + + +class DashTable(dash_table.DataTable): + def __init__( + self, data: List[dict], columns: List[dict], height: str = "none", **kwargs: Any + ) -> None: + super().__init__( + data=data, + columns=columns, + style_table={"height": height, **LayoutStyle.TABLE_STYLE}, + style_as_list_view=True, + css=LayoutStyle.TABLE_CSS, + style_header=LayoutStyle.TABLE_HEADER, + **kwargs, + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py new file mode 100644 index 000000000..9a284c79c --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py @@ -0,0 +1,19 @@ +class LayoutStyle: + MAIN_HEIGHT = "87vh" + HEADER = { + "font-size": "15px", + "color": "black", + "text-transform": "uppercase", + "border-color": "black", + } + TABLE_HEADER = {"fontWeight": "bold"} + TABLE_STYLE = {"max-height": MAIN_HEIGHT, "overflowY": "auto"} + TABLE_CELL_WIDTH = 95 + TABLE_CELL_HEIGHT = "10px" + TABLE_HIGHLIGHT = {"backgroundColor": "rgb(230, 230, 230)", "fontWeight": "bold"} + TABLE_CSS = [ + { + "selector": ".dash-spreadsheet tr", + "rule": f"height: {TABLE_CELL_HEIGHT};", + }, + ] diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py new file mode 100644 index 000000000..7689d5c08 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py @@ -0,0 +1,78 @@ +from typing import Optional + +import numpy as np +import pandas as pd +import plotly.graph_objects as go + +from webviz_subsurface._figures import create_figure + + +class MapFigure: + def __init__( + self, + dframe: pd.DataFrame, + color_by: str, + colormap: dict, + faultlinedf: Optional[pd.DataFrame] = None, + ): + self.dframe = dframe + self.color_by = color_by + self.colormap = colormap + self.hover_data = ["I", "J", "K"] + + self._figure = self.create_figure() + + if faultlinedf is not None: + self.add_fault_lines(faultlinedf) + + @property + def figure(self) -> go.Figure: + return self._figure + + @property + def axis_layout(self) -> dict: + return { + "title": None, + "showticklabels": False, + "showgrid": False, + "showline": False, + } + + def create_figure(self) -> go.Figure: + return ( + create_figure( + plot_type="scatter", + data_frame=self.dframe, + x="X", + y="Y", + color=self.color_by + if self.color_by != "PERMX" + else np.log10(self.dframe[self.color_by]), + color_discrete_map=self.colormap, + xaxis={"constrain": "domain", **self.axis_layout}, + yaxis={"scaleanchor": "x", **self.axis_layout}, + hover_data=[self.color_by] + self.hover_data, + color_continuous_scale="Viridis", + ) + .update_traces(marker_size=10, unselected={"marker": {"opacity": 0}}) + .update_coloraxes(showscale=False) + .update_layout( + plot_bgcolor="white", + margin={"t": 10, "b": 10, "l": 0, "r": 0}, + showlegend=False, + ) + ) + + def add_fault_lines(self, faultlinedf: pd.DataFrame) -> None: + for _fault, faultdf in faultlinedf.groupby("POLY_ID"): + self._figure.add_trace( + { + "x": faultdf["X_UTME"], + "y": faultdf["Y_UTMN"], + "mode": "lines", + "type": "scatter", + "hoverinfo": "none", + "showlegend": False, + "line": {"color": "grey", "width": 1}, + } + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py new file mode 100644 index 000000000..1b6053903 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py @@ -0,0 +1,166 @@ +import numpy as np +import pandas as pd +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +from webviz_subsurface._utils.colors import hex_to_rgb, rgb_to_str, scale_rgb_lightness + + +def axis_defaults(showgrid: bool = True) -> dict: + return { + "showline": True, + "linewidth": 2, + "linecolor": "black", + "mirror": True, + "showgrid": showgrid, + "gridwidth": 1, + "gridcolor": "lightgrey", + } + + +class PropertiesVsDepthSubplots: + def __init__( + self, + dframe: pd.DataFrame, + color_by: str, + colormap: dict, + discrete_color: bool = True, + ) -> None: + self.dframe = dframe + self.color_by = color_by + self.discrete_color = discrete_color + self.colormap = colormap + self.layout = [(1, 1), (1, 2), (2, 1), (2, 2)] + self.responses = ["SWATINIT", "SWAT", "PRESSURE", "PC"] + self.hover_data = self.create_hover_data( + include_columns=["QC_FLAG", "EQLNUM", "SATNUM", "I", "J", "K"] + ) + self.uirevision = "".join([str(x) for x in self.dframe["EQLNUM"].unique()]) + + self._figure = self.create_empty_subplots_figure(rows=2, cols=2) + self.add_subplotfigures_to_main_figure() + self.add_contacts_to_plot() + + @property + def figure(self) -> go.Figure: + return self._figure + + @property + def hovertemplate(self) -> go.Figure: + return ( + "X: %{x}
Y: %{y}
" + + "
".join( + [ + f"{col}:%{{customdata[{idx}]}}" + for idx, col in enumerate(self.hover_data) + ] + ) + + "" + ) + + def create_empty_subplots_figure(self, rows: int, cols: int) -> go.Figure: + return ( + make_subplots( + rows=rows, + cols=cols, + subplot_titles=[f"Depth vs {resp}" for resp in self.responses], + shared_yaxes=True, + vertical_spacing=0.07, + horizontal_spacing=0.05, + ) + .update_layout( + plot_bgcolor="white", + uirevision=self.uirevision, + margin={"t": 50, "b": 10, "l": 10, "r": 10}, + legend={"orientation": "h"}, + clickmode="event+select", + coloraxis={"colorscale": "Viridis", **self.colorbar}, + ) + .update_yaxes(autorange="reversed", **axis_defaults()) + .update_xaxes(axis_defaults()) + .update_xaxes(row=1, col=2, matches="x") + ) + + def add_subplotfigures_to_main_figure(self) -> None: + # for discrete colors there should be one trace per unique color + unique_traces = ( + self.dframe[self.color_by].unique() + if self.discrete_color + else [self.color_by] + ) + + for color in unique_traces: + df = self.dframe + df = df[df[self.color_by] == color] if self.discrete_color else df + customdata = np.stack([df[col] for col in self.hover_data], axis=-1) + + for idx, response in enumerate(self.responses): + trace = go.Scattergl( + x=df[response], + y=df["Z"], + mode="markers", + name=color, + showlegend=idx == 0, + marker=self.set_marker_style(color, df), + unselected={"marker": self.set_unselected_marker_style(color)}, + customdata=customdata, + hovertemplate=self.hovertemplate, + legendgroup=color, + ).update(marker_size=10) + + row, col = self.layout[idx] + self._figure.add_trace(trace, row=row, col=col) + + @property + def colorbar(self) -> dict: + if self.color_by != "PERMX": + return {} + tickvals = list(range(-4, 5, 1)) + return { + "colorbar": { + "tickvals": tickvals, + "ticktext": [10**val for val in tickvals], + } + } + + def create_hover_data(self, include_columns: list) -> list: + # ensure the colorby is the first entry in the list -> used in customdata in callback + hover_data = [self.color_by] + for col in include_columns: + if col not in hover_data: + hover_data.append(col) + return hover_data + + def set_marker_style(self, color: str, df: pd.DataFrame) -> dict: + if not self.discrete_color: + return { + "coloraxis": "coloraxis", + "color": df[self.color_by] + if self.color_by != "PERMX" + else np.log10(df[self.color_by]), + } + return {"color": self.colormap[color], "opacity": 0.5} + + def set_unselected_marker_style(self, color: str) -> dict: + if not self.discrete_color: + return {"opacity": 0.1} + return { + "color": rgb_to_str( + scale_rgb_lightness(hex_to_rgb(self.colormap[color]), 250) + ) + } + + def add_contacts_to_plot(self) -> None: + """Annotate axes with named horizontal lines for contacts.""" + for contact in ["OWC", "GWC", "GOC"]: + if contact in self.dframe and self.dframe["EQLNUM"].nunique() == 1: + # contacts are assumed constant in the dataframe + value = self.dframe[contact].values[0] + # do not include dummy contacts (shallower than the dataset) + if value > self.dframe["Z"].min(): + self._figure.add_hline( + value, + line={"color": "black", "dash": "dash", "width": 1.5}, + annotation_text=f"{contact}={value:g}", + annotation_position="bottom left", + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py new file mode 100644 index 000000000..c98de5dac --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py @@ -0,0 +1,118 @@ +from typing import List + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +from webviz_subsurface._figures import create_figure +from webviz_subsurface._utils.colors import hex_to_rgb, rgb_to_str, scale_rgb_lightness + +from .._plugin_ids import PlugInIDs + + +def axis_defaults(showgrid: bool = True) -> dict: + return { + "showline": True, + "linewidth": 2, + "linecolor": "black", + "mirror": True, + "showgrid": showgrid, + "gridwidth": 1, + "gridcolor": "lightgrey", + } + + +class WaterfallPlot: + # Ensure fixed order of plot elements: + ORDER = [ + "SWATINIT_WVOL", + PlugInIDs.QcFlags.SWL_TRUNC, + PlugInIDs.QcFlags.PPCWMAX, + PlugInIDs.QcFlags.FINE_EQUIL, + PlugInIDs.QcFlags.HC_BELOW_FWL, + PlugInIDs.QcFlags.SWATINIT_1, + "SWAT_WVOL", + ] + MEASURES = [ + "absolute", + "relative", + "relative", + "relative", + "relative", + "relative", + "total", + ] + + def __init__(self, qc_vols: dict) -> None: + # collect necessary values from input and make volume values more human friendly + self.qc_vols = { + key: (qc_vols[key] / (10**6)) + for key in self.ORDER + ["SWATINIT_HCVOL", "SWAT_HCVOL"] + } + self.qc_vols.update( + {key: qc_vols[key] for key in ["WVOL_DIFF_PERCENT", "HCVOL_DIFF_PERCENT"]} + ) + + @property + def range(self) -> list: + range_min = min(self.qc_vols["SWATINIT_WVOL"], self.qc_vols["SWAT_WVOL"]) * 0.95 + range_max = max(self.qc_vols["SWATINIT_WVOL"], self.qc_vols["SWAT_WVOL"]) * 1.05 + return [range_min, range_max] + + @property + def figure(self) -> go.Figure: + return ( + go.Figure( + go.Waterfall( + orientation="v", + measure=self.MEASURES, + x=self.ORDER, + textposition="outside", + text=self.create_bartext(), + y=[self.qc_vols[key] for key in self.ORDER], + connector={"mode": "spanning"}, + ) + ) + .update_yaxes( + title="Water Volume (Mrm3)", range=self.range, **axis_defaults() + ) + .update_xaxes( + type="category", + tickangle=-45, + tickfont_size=17, + **axis_defaults(showgrid=False), + ) + .update_layout( + plot_bgcolor="white", + title="Waterfall chart showing changes from SWATINIT to SWAT", + margin={"t": 50, "b": 50, "l": 50, "r": 50}, + ) + ) + + def create_bartext(self) -> List[str]: + """ + Create bartext for each qc_flag category with Water and HC volume change + relative to SWATINIT_WVOL in percent. + """ + text = [] + for bar_name in self.ORDER: + bartext = [f"{self.qc_vols[bar_name]:.2f} Mrm3"] + if bar_name != self.ORDER[0]: + bartext.append( + f"Water {self.get_water_diff_in_percent(bar_name):.1f} %" + ) + bartext.append(f"HC {self.get_hc_diff_in_percent(bar_name):.1f} %") + + text.append("
".join(bartext)) + return text + + def get_water_diff_in_percent(self, bar_name: str) -> float: + if bar_name == self.ORDER[-1]: + return self.qc_vols["WVOL_DIFF_PERCENT"] + return (self.qc_vols[bar_name] / self.qc_vols["SWATINIT_WVOL"]) * 100 + + def get_hc_diff_in_percent(self, bar_name: str) -> float: + if bar_name == self.ORDER[-1]: + return self.qc_vols["HCVOL_DIFF_PERCENT"] + if self.qc_vols["SWATINIT_HCVOL"] > 0: + return (-self.qc_vols[bar_name] / self.qc_vols["SWATINIT_HCVOL"]) * 100 + return 0 diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py new file mode 100644 index 000000000..b5bda40c8 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py @@ -0,0 +1,4 @@ +from ._info_box import InfoBox +from ._info_dialog import InfoDialog +from ._overview_table import OverviewTable +from .describtion import Describtion diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py new file mode 100644 index 000000000..495c2b670 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py @@ -0,0 +1,48 @@ +import webviz_core_components as wcc +from dash import dcc, html +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._layout_style import LayoutStyle + + +class Describtion(ViewElementABC): + class IDs: + TEXT = "text" + + def __init__( + self, + ) -> None: + super().__init__() + + def inner_layout(self) -> html.Div: + return html.Div( + style={"margin-top": "20px"}, + id=Describtion.IDs.TEXT, + children=[ + wcc.Header("QC_FLAG descriptions", style=LayoutStyle.HEADER), + dcc.Markdown(qc_flag_description()), + ], + ) + + +def qc_flag_description() -> str: # listen på bunnen av første side + return """ +- **PC_SCALED** - Capillary pressure have been scaled and SWATINIT was accepted. +- **FINE_EQUIL** - If item 9 in EQUIL is nonzero then initialization happens in a vertically + refined model. Capillary pressure is still scaled, but water might be added or lost. +- **SWL_TRUNC** - If SWL is larger than SWATINIT, SWAT will be reset to SWL. Extra water is + added and hydrocarbons are lost. +- **SWATINIT_1** - When SWATINIT is 1 above the contact, Eclipse will ignore SWATINIT and not + touch the capillary pressure function which typically results in extra hydrocarbons. + This could be ok as long as the porosities and/or permeabilities of these cells are small. + If SWU is included, cells where SWATINIT is equal or larger than SWU will + also be flagged as SWATINIT_1 +- **HC_BELOW_FWL** - If SWATINIT is less than 1 below the contact provided in EQUIL, Eclipse will + ignore it and not scale the capillary pressure function. SWAT will be 1, unless a capillary + pressure function with negative values is in SWOF/SWFN. This results in the loss of HC volumes. +- **PPCWMAX** - If an upper limit of how much capillary pressure scaling is allowed is set, water will be + lost if this limit is hit. +- **WATER** - SWATINIT was 1 in the water zone, and SWAT is set to 1. +> **Consult the [check_swatinit](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) + documentation for more detailed descriptions** +""" diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py new file mode 100644 index 000000000..b1e50e6dc --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py @@ -0,0 +1,48 @@ +import webviz_core_components as wcc +from dash import html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._layout_style import LayoutStyle + + +class InfoBox(ViewElementABC): + class IDs: + # pylint disable=too-few-public-methods + FRAME = "frame" + KEY_NUMBERS = "key-numbers" + + def __init__(self, datamodel, informaiton_dialog) -> None: + super().__init__() + max_pc, min_pc = datamodel.pc_scaling_min_max + wvol, hcvol = datamodel.vol_diff_total + self.information_dialog = informaiton_dialog + self.number_style = { + "font-weight": "bold", + "font-size": "17px", + "margin-left": "20px", + } + self.data = [ + ("HC Volume difference:", f"{hcvol:.2f} %"), + ("Water Volume difference:", f"{wvol:.2f} %"), + ("Maximum Capillary Pressure scaling:", f"{max_pc:.1f}"), + ("Minimum Capillary Pressure scaling:", f"{min_pc:.3g}"), + ] + + def inner_layout(self) -> Component: + return wcc.Frame( + style={"height": "90%"}, + id=self.register_component_unique_id(InfoBox.IDs.FRAME), + children=[ + wcc.Header("Information", style=LayoutStyle.HEADER), + self.information_dialog, + wcc.Header("Key numbers", style=LayoutStyle.HEADER), + html.Div( + [ + html.Div([text, html.Span(num, style=self.number_style)]) + for text, num in self.data + ], + id=self.register_component_unique_id(InfoBox.IDs.KEY_NUMBERS), + ), + ], + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py new file mode 100644 index 000000000..792ad720f --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py @@ -0,0 +1,50 @@ +import webviz_core_components as wcc +from dash import dcc, html +from webviz_config.webviz_plugin_subclasses import ViewElementABC + + +class InfoDialog(ViewElementABC): + class IDs: + # pylint:diable=too-few-public-methods + BUTTON = "button" + INFO_DIALOG = "info-dialog" + + def __init__( + self, + ) -> None: + super().__init__() + self.title = "Plugin and 'check_swatinit' information" + + def inner_layout(self) -> html.Div: + return html.Div( + style={"margin-bottom": "30px"}, + children=[ + html.Button( + "CLICK HERE FOR INFORMATION", # var egt samme som tittelen til wcc.Dialog + style={"width": "100%", "background-color": "white"}, + id=self.register_component_unique_id(InfoDialog.IDs.BUTTON), + ), + wcc.Dialog( + title=self.title, + id=self.register_component_unique_id(InfoDialog.IDs.INFO_DIALOG), + max_width="md", + open=False, + children=dcc.Markdown(check_swatinit_description()), + ), + ], + ) + + +def check_swatinit_description() -> str: + return """ +This plugin is used to visualize the output from **check_swatinit** which is a **QC tool for Water Initialization in Eclipse runs +when the SWATINIT keyword has been used**. It is used to quantify how much the volume changes from **SWATINIT** to **SWAT** at time +zero in the dynamical model, and help understand why it changes. +When the keyword SWATINIT has been used as water initialization option in Eclipse, capillary pressure scaling on a cell-by-cell basis will +occur in order to match SWATINIT from the geomodel. +This process has some exceptions which can cause volume changes from SWATINIT to SWAT at time zero. +Each cell in the dynamical model has been flagged according to what has happened during initialization, and information is stored +in the **QC_FLAG** column. +> Check the maximum capillary pressure pr SATNUM in each EQLNUM to ensure extreme values were not necessary +[check_swatinit documentation](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) +""" diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py new file mode 100644 index 000000000..bdc1823e6 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py @@ -0,0 +1,38 @@ +from dash import html +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._dash_table import DashTable +from .._layout_style import LayoutStyle + + +class OverviewTable(ViewElementABC): + class IDs: + LABEL = "label" + TABLE = "table" + + def __init__(self, datamodel) -> None: + super().__init__() + self.data, self.columns = datamodel.table_data_qc_vol_overview() + self.label = ( + "Table showing volume changes from SWATINIT to SWAT at Reservoir conditions" + ) + + def inner_layout(self) -> html.Div: + return html.Div( + children=[ + html.Div( + html.Label(self.label, className="webviz-underlined-label"), + style={"margin-bottom": "20px"}, + ), + DashTable( + data=self.data, + columns=self.columns, + style_data_conditional=[ + { + "if": {"row_index": [0, len(self.data) - 1]}, + **LayoutStyle.TABLE_HIGHLIGHT, + }, + ], + ), + ], + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/views/__init__.py b/webviz_subsurface/plugins/_swatint_qc/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py new file mode 100644 index 000000000..1dbc118d9 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py @@ -0,0 +1,143 @@ +class TabMaxPcInfoLayout: + def __init__(self, get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: + self.datamodel = datamodel + self.get_uuid = get_uuid + + def main_layout( + self, dframe: pd.DataFrame, selectors: list, map_figure: go.Figure + ) -> html.Div: + return html.Div( + children=[ + wcc.Header( + "Maximum capillary pressure scaling", style=LayoutStyle.HEADER + ), + wcc.FlexBox( + style={"margin-top": "10px", "height": "40vh"}, + children=[ + wcc.FlexColumn( + dcc.Markdown(pc_columns_description()), + flex=7, + style={"margin-right": "40px"}, + ), + wcc.FlexColumn( + FullScreen( + wcc.Graph( + style={"height": "100%", "min-height": "35vh"}, + figure=map_figure, + ) + ), + flex=3, + ), + ], + ), + self.create_max_pc_table(dframe, text_columns=selectors), + ] + ) + + @property + def selections_layout(self) -> html.Div: + return html.Div( + children=[ + wcc.Selectors( + label="Selections", + children=[ + html.Div( + wcc.RadioItems( + label="Split table by:", + id=self.get_uuid(LayoutElements.GROUPBY_EQLNUM), + options=[ + {"label": "SATNUM", "value": "SATNUM"}, + {"label": "SATNUM and EQLNUM", "value": "both"}, + ], + value="SATNUM", + ), + style={"margin-bottom": "10px"}, + ), + self.scaling_threshold, + ], + ), + wcc.Selectors( + label="Filters", + children=[ + wcc.SelectWithLabel( + label="EQLNUM", + id=self.get_uuid(LayoutElements.TABLE_EQLNUM_SELECTOR), + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.eqlnums + ], + value=self.datamodel.eqlnums, + size=min(8, len(self.datamodel.eqlnums)), + multi=True, + ), + range_filters( + self.get_uuid(LayoutElements.FILTERS_CONTINOUS_MAX_PC), + self.datamodel, + ), + ], + ), + ] + ) + + def create_max_pc_table( + self, dframe: pd.DataFrame, text_columns: list + ) -> dash_table: + return DashTable( + data=dframe.to_dict("records"), + columns=[ + { + "name": i, + "id": i, + "type": "numeric" if i not in text_columns else "text", + "format": {"specifier": ".4~r"} if i not in text_columns else {}, + } + for i in dframe.columns + ], + height="48vh", + sort_action="native", + fixed_rows={"headers": True}, + style_cell={ + "minWidth": LayoutStyle.TABLE_CELL_WIDTH, + "maxWidth": LayoutStyle.TABLE_CELL_WIDTH, + "width": LayoutStyle.TABLE_CELL_WIDTH, + }, + style_data_conditional=[ + { + "if": { + "filter_query": f"{{{self.datamodel.COLNAME_THRESHOLD}}} > 0", + }, + **LayoutStyle.TABLE_HIGHLIGHT, + }, + ], + ) + + @property + def scaling_threshold(self) -> html.Div: + return html.Div( + style={"margin-top": "10px"}, + children=[ + wcc.Label("Maximum PC_SCALING threshold"), + html.Div( + dcc.Input( + id=self.get_uuid(LayoutElements.HIGHLIGHT_ABOVE), + type="number", + persistence=True, + persistence_type="session", + ) + ), + ], + ) + + +# pylint: disable=anomalous-backslash-in-string +def pc_columns_description() -> str: # tekst i figuren på siste side + return f""" +> **Column descriptions** +> - **PCOW_MAX** - Maximum capillary pressure from the input SWOF/SWFN tables +> - **PC_SCALING** - Maximum capillary pressure scaling applied +> - **PPCW** - Maximum capillary pressure after scaling +> - **{SwatinitQcDataModel.COLNAME_THRESHOLD}** - Column showing how many percent of the pc-scaled dataset that match the user-selected threshold +*PPCW = PCOW_MAX \* PC_SCALING* +A threshold for the maximum capillary scaling can be set in the menu. +The table will show how many percent of the dataset that exceeds this value, and cells above the threshold will be shown in the map ➡️ +""" diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py new file mode 100644 index 000000000..e9bd5994c --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py @@ -0,0 +1,59 @@ +from pathlib import Path +from typing import Dict, List, Optional, Tuple, Union + +import pandas as pd +from pyparsing import DebugExceptionAction, col +import webviz_core_components as wcc +from dash import ( + Input, + Output, + State, + callback, + callback_context, + clientside_callback, + dcc, + html, +) +from webviz_config.webviz_plugin_subclasses import ViewABC + +from .._plugin_ids import PlugInIDs +from ..view_elements import OverViewTable, InfoBox, InfoDialog, Describtion + + +class OverviewTabLayout(ViewABC): + class IDs: + # pylint: disable=too-few-public-methods + TABLE = "table" + INFO_BOX = "info-box" + INFO_DIALOG = "info-dialog" + DESCRIBTION = "describtion" + MAIN_CLOUMN = "main-column" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__() + self.datamodel = datamodel + + @property + def main_layout(self) -> html.Div: # burde det være noen IDs her? + return html.Div( + children=[ + html.Div( + style={"height": "40vh", "overflow-y": "auto"}, + children=[ + wcc.FlexBox( + children=[ + wcc.FlexColumn( + [ + OverViewTable(self.datamodel), + ], + flex=7, + style={"margin-right": "20px"}, + ), + wcc.FlexColumn(InfoBox(self.datamodel), flex=3), + ], + ), + ], + ), + Describtion(), + ] + ) diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py new file mode 100644 index 000000000..00b3943c2 --- /dev/null +++ b/webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py @@ -0,0 +1,166 @@ + + class TabQqPlotLayout: + class MainPlots(str, Enum): + WATERFALL = auto() + PROP_VS_DEPTH = auto() + + def __init__(self, get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: + self.datamodel = datamodel + self.get_uuid = get_uuid + + def main_layout( + self, + main_figure: go.Figure, + map_figure: go.Figure, + qc_volumes: dict, + ) -> wcc.FlexBox: + return wcc.FlexBox( + children=[ + wcc.FlexColumn( + flex=4, + children=wcc.Graph( + style={"height": "85vh"}, + id=self.get_uuid(LayoutElements.MAIN_FIGURE), + figure=main_figure, + ), + ), + wcc.FlexColumn( + flex=1, + children=[ + FullScreen( + wcc.Graph( + responsive=True, + style={"height": "100%", "min-height": "45vh"}, + id=self.get_uuid(LayoutElements.MAP_FIGURE), + figure=map_figure, + ) + ), + self.info_box(qc_volumes, height="35vh"), + ], + ), + ] + ) + + @property + def selections_layout(self) -> html.Div: + return html.Div( + children=[ + html.Div( + wcc.Dropdown( + label="Select QC-visualization:", + id=self.get_uuid(LayoutElements.PLOT_SELECTOR), + options=[ + { + "label": "Waterfall plot for water vol changes", + "value": self.MainPlots.WATERFALL, + }, + { + "label": "Reservoir properties vs Depth", + "value": self.MainPlots.PROP_VS_DEPTH, + }, + ], + value=self.MainPlots.PROP_VS_DEPTH, + clearable=False, + ), + style={"margin-bottom": "15px"}, + ), + wcc.Selectors( + label="Selections", + children=[ + wcc.SelectWithLabel( + label="EQLNUM", + id=self.get_uuid(LayoutElements.PLOT_EQLNUM_SELECTOR), + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.eqlnums + ], + value=self.datamodel.eqlnums[:1], + size=min(8, len(self.datamodel.eqlnums)), + multi=True, + ), + wcc.Dropdown( + label="Color by", + id=self.get_uuid(LayoutElements.COLOR_BY), + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.color_by_selectors + ], + value="QC_FLAG", + clearable=False, + ), + wcc.Label("Max number of points:"), + dcc.Input( + id=self.get_uuid(LayoutElements.MAX_POINTS), + type="number", + value=5000, + ), + ], + ), + wcc.Selectors( + label="Filters", + children=[ + wcc.SelectWithLabel( + label="QC_FLAG", + id={ + "id": self.get_uuid(LayoutElements.FILTERS_DISCRETE), + "col": "QC_FLAG", + }, + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.qc_flag + ], + value=self.datamodel.qc_flag, + size=min(8, len(self.datamodel.qc_flag)), + ), + wcc.SelectWithLabel( + label="SATNUM", + id={ + "id": self.get_uuid(LayoutElements.FILTERS_DISCRETE), + "col": "SATNUM", + }, + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.satnums + ], + value=self.datamodel.satnums, + size=min(8, len(self.datamodel.satnums)), + ), + range_filters( + self.get_uuid(LayoutElements.FILTERS_CONTINOUS), + self.datamodel, + ), + ], + ), + ], + ) + + @staticmethod + def info_box(qc_vols: dict, height: str) -> html.Div: + return html.Div( + [ + wcc.Header("Information about selection", style=LayoutStyle.HEADER), + html.Div( + "EQLNUMS:", style={"font-weight": "bold", "font-size": "15px"} + ), + html.Div(", ".join([str(x) for x in qc_vols["EQLNUMS"]])), + html.Div( + "SATNUMS:", style={"font-weight": "bold", "font-size": "15px"} + ), + html.Div(", ".join([str(x) for x in qc_vols["SATNUMS"]])), + html.Div( + html.Span("Reservoir Volume Difference:"), + style={"font-weight": "bold", "margin-top": "10px"}, + ), + html.Div( + children=[ + html.Div(line) + for line in [ + f"Water Volume Diff: {qc_vols['WVOL_DIFF']/(10**6):.2f} Mrm3", + f"Water Volume Diff (%): {qc_vols['WVOL_DIFF_PERCENT']:.2f}", + f"HC Volume Diff (%): {qc_vols['HCVOL_DIFF_PERCENT']:.2f}", + ] + ], + ), + ], + style={"height": height, "padding": "10px"}, + ) From 5c47c3da19dd2d2605d5eb8b028dcae16e37a37b Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Mon, 1 Aug 2022 16:00:35 +0200 Subject: [PATCH 2/7] save vol 2 --- .../view_elements/overview_elements/_info_box.py | 6 +++--- .../plugins/_swatint_qc/views/_overview_info.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py index b1e50e6dc..dab41ed79 100644 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py +++ b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py @@ -4,6 +4,7 @@ from webviz_config.webviz_plugin_subclasses import ViewElementABC from .._layout_style import LayoutStyle +from ._info_dialog import InfoDialog class InfoBox(ViewElementABC): @@ -12,11 +13,10 @@ class IDs: FRAME = "frame" KEY_NUMBERS = "key-numbers" - def __init__(self, datamodel, informaiton_dialog) -> None: + def __init__(self, datamodel) -> None: super().__init__() max_pc, min_pc = datamodel.pc_scaling_min_max wvol, hcvol = datamodel.vol_diff_total - self.information_dialog = informaiton_dialog self.number_style = { "font-weight": "bold", "font-size": "17px", @@ -35,7 +35,7 @@ def inner_layout(self) -> Component: id=self.register_component_unique_id(InfoBox.IDs.FRAME), children=[ wcc.Header("Information", style=LayoutStyle.HEADER), - self.information_dialog, + InfoDialog(), wcc.Header("Key numbers", style=LayoutStyle.HEADER), html.Div( [ diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py index e9bd5994c..433ad7b24 100644 --- a/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py +++ b/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py @@ -1,8 +1,8 @@ from pathlib import Path +from subprocess import call from typing import Dict, List, Optional, Tuple, Union import pandas as pd -from pyparsing import DebugExceptionAction, col import webviz_core_components as wcc from dash import ( Input, @@ -14,10 +14,11 @@ dcc, html, ) +from pyparsing import DebugExceptionAction, col from webviz_config.webviz_plugin_subclasses import ViewABC from .._plugin_ids import PlugInIDs -from ..view_elements import OverViewTable, InfoBox, InfoDialog, Describtion +from ..view_elements import Describtion, InfoBox, InfoDialog, OverViewTable class OverviewTabLayout(ViewABC): @@ -57,3 +58,8 @@ def main_layout(self) -> html.Div: # burde det være noen IDs her? Describtion(), ] ) + + def set_callbacks(self) -> None: + @callback( + + ) From c35d4c7cc417c393110618ab30c69ea87f4f6dc1 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Tue, 2 Aug 2022 12:50:18 +0200 Subject: [PATCH 3/7] first tab working --- webviz_subsurface/plugins/__init__.py | 2 +- .../plugins/_swatinit_qc/__init__.py | 2 + .../{_swatint_qc => _swatinit_qc}/_error.py | 0 .../{_swatint_qc => _swatinit_qc}/_plugin.py | 22 +- .../_plugin_ids.py | 22 ++ .../plugins/_swatinit_qc/_swatint.py | 330 ++++++++++++++++++ .../settings_groups}/__init__.py | 0 .../settings_groups/_water_settings.py | 172 +++++++++ .../shared_settings/__init__.py | 0 .../shared_settings/_filter.py | 0 .../shared_settings/_pick_tab.py | 4 +- .../_swatinit_qc/view_elements/__init__.py | 3 + .../view_elements/_dash_table.py | 0 .../view_elements/_layout_style.py | 0 .../view_elements/_map_figure.py | 0 .../view_elements/_overview_layout.py | 188 ++++++++++ .../view_elements/_properties_vs_depth.py | 0 .../view_elements/_waterfall_plot.py | 0 .../plugins/_swatinit_qc/views/__init__.py | 1 + .../views/_capilary_preassure.py | 0 .../_swatinit_qc/views/_overview_info.py | 46 +++ .../views/_water_initialization.py | 45 ++- .../plugins/_swatint_qc/__init__.py | 1 - .../_swatint_qc/view_elements/__init__.py | 3 - .../overview_elements/__init__.py | 4 - .../overview_elements/_describtion.py | 48 --- .../overview_elements/_info_box.py | 48 --- .../overview_elements/_info_dialog.py | 50 --- .../overview_elements/_overview_table.py | 38 -- .../_swatint_qc/views/_overview_info.py | 65 ---- 30 files changed, 822 insertions(+), 272 deletions(-) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/__init__.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/_error.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/_plugin.py (60%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/_plugin_ids.py (66%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/_swatint.py rename webviz_subsurface/plugins/{_swatint_qc/views => _swatinit_qc/settings_groups}/__init__.py (100%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/shared_settings/__init__.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/shared_settings/_filter.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/shared_settings/_pick_tab.py (96%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/view_elements/_dash_table.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/view_elements/_layout_style.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/view_elements/_map_figure.py (100%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/view_elements/_properties_vs_depth.py (100%) rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/view_elements/_waterfall_plot.py (100%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/__init__.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/views/_capilary_preassure.py (100%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py rename webviz_subsurface/plugins/{_swatint_qc => _swatinit_qc}/views/_water_initialization.py (85%) delete mode 100644 webviz_subsurface/plugins/_swatint_qc/__init__.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py delete mode 100644 webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py diff --git a/webviz_subsurface/plugins/__init__.py b/webviz_subsurface/plugins/__init__.py index f019ed49a..27a8548ac 100644 --- a/webviz_subsurface/plugins/__init__.py +++ b/webviz_subsurface/plugins/__init__.py @@ -57,7 +57,7 @@ from ._surface_viewer_fmu import SurfaceViewerFMU from ._surface_with_grid_cross_section import SurfaceWithGridCrossSection from ._surface_with_seismic_cross_section import SurfaceWithSeismicCrossSection -from ._swatinit_qc_old import SwatinitQC +from ._swatinit_qc import SwatinitQC from ._tornado_plotter_fmu import TornadoPlotterFMU from ._volumetric_analysis import VolumetricAnalysis from ._well_analysis import WellAnalysis diff --git a/webviz_subsurface/plugins/_swatinit_qc/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/__init__.py new file mode 100644 index 000000000..3d966a009 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/__init__.py @@ -0,0 +1,2 @@ +from ._plugin import SwatinitQC +from ._swatint import SwatinitQcDataModel diff --git a/webviz_subsurface/plugins/_swatint_qc/_error.py b/webviz_subsurface/plugins/_swatinit_qc/_error.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/_error.py rename to webviz_subsurface/plugins/_swatinit_qc/_error.py diff --git a/webviz_subsurface/plugins/_swatint_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py similarity index 60% rename from webviz_subsurface/plugins/_swatint_qc/_plugin.py rename to webviz_subsurface/plugins/_swatinit_qc/_plugin.py index 80465e4d3..2fe4a2c86 100644 --- a/webviz_subsurface/plugins/_swatint_qc/_plugin.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py @@ -9,7 +9,9 @@ from ._error import error from ._plugin_ids import PlugInIDs +from ._swatint import SwatinitQcDataModel from .shared_settings import PickTab +from .views import OverviewTabLayout class SwatinitQC(WebvizPluginABC): @@ -30,13 +32,23 @@ def __init__( realization=realization, faultlines=faultlines, ) + self.error_message = "" - self.add_store(PlugInIDs.Stores.Shared.PICK_VIEW) - self.add_shared_settings_group(PickTab) + self.add_store( + PlugInIDs.Stores.Shared.PICK_VIEW, WebvizPluginABC.StorageType.SESSION + ) + self.add_shared_settings_group(PickTab(), PlugInIDs.SharedSettings.PICK_VIEW) + + self.add_store( + PlugInIDs.Stores.Overview.BUTTON, WebvizPluginABC.StorageType.SESSION + ) - self.add_store(PlugInIDs.Stores.Shared.EQLNUM) - self.add_shared_settings_group() + self.add_view( + OverviewTabLayout(self._datamodel), + PlugInIDs.ViewGroups.Overview.OVERVIEW_TAB, + PlugInIDs.ViewGroups.Overview.GROUP_NAME, + ) @property def layout(self) -> wcc.Tabs: - return plugin_main_layout(self.uuid, self._datamodel) + return error(self.error_message) diff --git a/webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py similarity index 66% rename from webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py rename to webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py index 915c5bcca..eacc146d1 100644 --- a/webviz_subsurface/plugins/_swatint_qc/_plugin_ids.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py @@ -13,9 +13,14 @@ class Shared: PICK_VIEW = "pick-view" EQLNUM = "eqlnum" + class Overview: + # pylint: disable=too-few-public-methods + BUTTON = "button" + class Water: # pylint: disable=too-few-public-methods QC_VIZ = "qc-viz" + EQLNUM = "eqlnum" COLOR_BY = "color_by" MAX_POINTS = "max-points" QC_FLAG = "qc-flag" @@ -41,3 +46,20 @@ class QcFlags: SWL_TRUNC = "SWL_TRUNC" UNKNOWN = "UNKNOWN" WATER = "WATER" + + class ViewGroups: + # pylint: disable=too-few-public-methods + class Overview: + # pylint: disable=too-few-public-methods + OVERVIEW_TAB = "overview-tab" + GROUP_NAME = "overview-group" + + class Water: + # pylint: disable=too-few-public-methods + WATER_TAB = "water-tab" + GROUP_NAME = "group-name" + + class Capilar: + # pylint: disable=too-few-public-methods + CAPILAR_TAB = "capilar-tab" + GROUP_NAEME = "group-name" diff --git a/webviz_subsurface/plugins/_swatinit_qc/_swatint.py b/webviz_subsurface/plugins/_swatinit_qc/_swatint.py new file mode 100644 index 000000000..ea4ce335b --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/_swatint.py @@ -0,0 +1,330 @@ +import re +from enum import Enum +from pathlib import Path +from typing import Callable, Dict, List, Optional, Tuple + +import numpy as np +import pandas as pd +from webviz_config import WebvizSettings +from webviz_config.common_cache import CACHE +from webviz_config.webviz_store import webvizstore + + +class QcFlags(str, Enum): + """Constants for use by check_swatinit""" + + FINE_EQUIL = "FINE_EQUIL" + HC_BELOW_FWL = "HC_BELOW_FWL" + PC_SCALED = "PC_SCALED" + PPCWMAX = "PPCWMAX" + SWATINIT_1 = "SWATINIT_1" + SWL_TRUNC = "SWL_TRUNC" + UNKNOWN = "UNKNOWN" + WATER = "WATER" + + +class SwatinitQcDataModel: + """Class keeping the data needed in the vizualisations and various + data providing methods. + """ + + COLNAME_THRESHOLD = "HC cells above threshold (%)" + SELECTORS = ["QC_FLAG", "SATNUM", "EQLNUM", "FIPNUM"] + + DROP_COLUMNS = [ + "Z_MIN", + "Z_MAX", + "GLOBAL_INDEX", + "SWATINIT_SWAT", + "SWATINIT_SWAT_WVOL", + "SWL_x", + "SWL_y", + "Z_DATUM", + "PRESSURE_DATUM", + ] + + def __init__( + self, + webviz_settings: WebvizSettings, + csvfile: str, + ensemble: str = None, + realization: Optional[int] = None, + faultlines: Optional[Path] = None, + ): + + self._theme = webviz_settings.theme + self._faultlines = faultlines + self.faultlines_df = read_csv(faultlines) if faultlines else None + + if ensemble is not None: + if isinstance(ensemble, list): + raise TypeError( + 'Incorrent argument type, "ensemble" must be a string instead of a list' + ) + if realization is None: + raise ValueError('Incorrent arguments, "realization" must be specified') + + ens_path = webviz_settings.shared_settings["scratch_ensembles"][ensemble] + # replace realization in string from scratch_ensemble with input realization + ens_path = re.sub( + "realization-[^/]", f"realization-{realization}", ens_path + ) + self.csvfile = Path(ens_path) / csvfile + else: + self.csvfile = Path(csvfile) + + self.dframe = read_csv(self.csvfile) + self.dframe.drop(columns=self.DROP_COLUMNS, errors="ignore", inplace=True) + + for col in self.SELECTORS + ["OWC", "GWC", "GOC"]: + if col in self.dframe: + self.dframe[col] = self.dframe[col].astype("category") + + self._initial_qc_volumes = self.compute_qc_volumes(self.dframe) + + @property + def webviz_store(self) -> List[Tuple[Callable, List[dict]]]: + return [ + ( + read_csv, + [ + {"csv_file": path} + for path in [self._faultlines, self.csvfile] + if path is not None + ], + ) + ] + + @property + def colors(self) -> List[str]: + return self._theme.plotly_theme["layout"]["colorway"] + + @property + def qc_flag_colors(self) -> Dict[str, str]: + """Predefined colors for the QC_FLAG column""" + return { + QcFlags.FINE_EQUIL.value: self.colors[8], + QcFlags.HC_BELOW_FWL.value: self.colors[5], + QcFlags.PC_SCALED.value: self.colors[2], + QcFlags.PPCWMAX.value: self.colors[9], + QcFlags.SWATINIT_1.value: self.colors[6], + QcFlags.SWL_TRUNC.value: self.colors[3], + QcFlags.UNKNOWN.value: self.colors[1], + QcFlags.WATER.value: self.colors[0], + } + + @property + def eqlnums(self) -> List[str]: + return sorted(list(self.dframe["EQLNUM"].unique()), key=int) + + @property + def satnums(self) -> List[str]: + return sorted(list(self.dframe["SATNUM"].unique()), key=int) + + @property + def qc_flag(self) -> List[str]: + return sorted(list(self.dframe["QC_FLAG"].unique())) + + @property + def filters_discrete(self) -> List[str]: + return ["QC_FLAG", "SATNUM"] + + @property + def filters_continuous(self) -> List[str]: + return ["Z", "PC", "SWATINIT", "PERMX", "PORO"] + + @property + def color_by_selectors(self) -> List[str]: + return self.SELECTORS + ["PERMX", "PORO"] + + @property + def pc_scaling_min_max(self) -> Tuple[float, float]: + return (self.dframe["PC_SCALING"].max(), self.dframe["PC_SCALING"].min()) + + @property + def vol_diff_total(self) -> Tuple[float, float]: + return ( + self._initial_qc_volumes["WVOL_DIFF_PERCENT"], + self._initial_qc_volumes["HCVOL_DIFF_PERCENT"], + ) + + def get_dataframe( + self, + filters: Optional[dict] = None, + range_filters: Optional[dict] = None, + ) -> pd.DataFrame: + + df = self.dframe.copy() + filters = filters if filters is not None else {} + range_filters = range_filters if range_filters is not None else {} + + for filt, value in filters.items(): + df = df[df[filt].isin(value)] + + for filt, value in range_filters.items(): + min_val, max_val = value + df = df[(df[filt] >= min_val) & (df[filt] <= max_val) | (df[filt].isnull())] + + return df + + @staticmethod + def resample_dataframe(dframe: pd.DataFrame, max_points: int) -> pd.DataFrame: + """Resample a dataframe to max number of points. The sampling will be + weighted in order to avoid removal of points that has an important qc_flag. + Points will mostly be removed if they are flagged as "WATER" or "PC_SCALED" + """ + if dframe.shape[0] > max_points: + dframe = dframe.copy() + dframe["sample_weight"] = 1 + dframe.loc[dframe["QC_FLAG"] == "WATER", "sample_weight"] = 0.1 + dframe.loc[dframe["QC_FLAG"] == "PC_SCALED", "sample_weight"] = 0.5 + return dframe.sample(max_points, weights=dframe["sample_weight"]) + return dframe + + @staticmethod + def filter_dframe_on_depth(dframe: pd.DataFrame) -> pd.DataFrame: + """Suggest a deep depth limit for what to plot, in order to avoid + showing too much of a less interesting water zone + """ + max_z = dframe["Z"].max() + hc_dframe = dframe[dframe["SWATINIT"] < 1] + if not hc_dframe.empty: + lowest_hc = hc_dframe["Z"].max() + hc_height = lowest_hc - dframe["Z"].min() + # Suggest to visualize a water height of 10% of the hc zone: + max_z = lowest_hc + 0.2 * hc_height + + return dframe[dframe["Z"] <= max_z] + + @staticmethod + def compute_qc_volumes(dframe: pd.DataFrame) -> dict: + """Compute numbers relevant for QC of saturation initialization of a + reservoir model. + Different volume numbers are typically related to the different QC_FLAG + """ + qc_vols: dict = {} + + # Ensure all QCFlag categories are represented: + for qc_cat in QcFlags: + qc_vols[qc_cat.value] = 0.0 + + # Overwrite dict values with correct figures: + for qc_cat, qc_df in dframe.groupby("QC_FLAG"): + qc_vols[qc_cat] = ( + (qc_df["SWAT"] - qc_df["SWATINIT"]) * qc_df["PORV"] + ).sum() + + if "VOLUME" in dframe: + qc_vols["VOLUME"] = dframe["VOLUME"].sum() + + qc_vols["PORV"] = dframe["PORV"].sum() + qc_vols["SWATINIT_WVOL"] = (dframe["SWATINIT"] * dframe["PORV"]).sum() + qc_vols["SWATINIT_HCVOL"] = qc_vols["PORV"] - qc_vols["SWATINIT_WVOL"] + qc_vols["SWAT_WVOL"] = (dframe["SWAT"] * dframe["PORV"]).sum() + qc_vols["SWAT_HCVOL"] = qc_vols["PORV"] - qc_vols["SWAT_WVOL"] + + # compute difference columns + qc_vols["WVOL_DIFF"] = qc_vols["SWAT_WVOL"] - qc_vols["SWATINIT_WVOL"] + qc_vols["WVOL_DIFF_PERCENT"] = ( + qc_vols["WVOL_DIFF"] / qc_vols["SWATINIT_WVOL"] + ) * 100 + qc_vols["HCVOL_DIFF"] = qc_vols["SWAT_HCVOL"] - qc_vols["SWATINIT_HCVOL"] + qc_vols["HCVOL_DIFF_PERCENT"] = ( + ((qc_vols["HCVOL_DIFF"] / qc_vols["SWATINIT_HCVOL"]) * 100) + if qc_vols["HCVOL_DIFF"] != 0.0 + else 0 + ) + qc_vols["EQLNUMS"] = sorted(dframe["EQLNUM"].unique()) + qc_vols["SATNUMS"] = sorted(dframe["SATNUM"].unique()) + + return qc_vols + + def create_colormap(self, color_by: str) -> dict: + """Create a colormap to ensure that the subplot and the mapfigure + has the same color for the same unique value. If 'QC_FLAG' is used as + color column, predefined colors are used. + """ + + return ( + dict(zip(self.dframe[color_by].unique(), self.colors * 10)) + if color_by != "QC_FLAG" + else self.qc_flag_colors + ) + + def get_max_pc_info_and_percent_for_data_matching_condition( + self, + dframe: pd.DataFrame, + condition: Optional[int], + groupby_eqlnum: bool = True, + ) -> pd.DataFrame: + def get_percent_of_match(df: pd.DataFrame, condition: Optional[int]) -> float: + df = df[df["QC_FLAG"] == "PC_SCALED"] + if condition is None or df.empty: + return np.nan + return (len(df[df["PC_SCALING"] >= condition]) / len(df)) * 100 + + groupby = ["SATNUM"] if not groupby_eqlnum else ["EQLNUM", "SATNUM"] + df_group = dframe.groupby(groupby) + df = df_group.max()[["PCOW_MAX", "PPCW", "PC_SCALING"]].round(6) + df[self.COLNAME_THRESHOLD] = df_group.apply( + lambda x: get_percent_of_match(x, condition) + ) + return df.reset_index().sort_values(groupby, key=lambda col: col.astype(int)) + + def table_data_qc_vol_overview(self) -> tuple: + """Return data and columns for dash_table showing overview of qc volumes""" + + skip_if_zero = [QcFlags.UNKNOWN.value, QcFlags.WATER.value] + column_order = [ + "", + "Response", + "Water Volume Diff", + "HC Volume Diff", + "Water Volume Mrm3", + "HC Volume Mrm3", + ] + qc_vols = self._initial_qc_volumes + + table_data = [] + # First report the SWATINIT volumes + table_data.append( + { + "Response": "SWATINIT", + "Water Volume Mrm3": f"{qc_vols['SWATINIT_WVOL']/1e6:>10.3f}", + "HC Volume Mrm3": f" {qc_vols['SWATINIT_HCVOL']/1e6:>8.3f}", + } + ) + # Then report the volume change per QC_FLAG + for key in [x.value for x in QcFlags]: + if key in skip_if_zero and np.isclose(qc_vols[key], 0, atol=1): + # Tolerance is 1 rm3, which is small in relevant contexts. + continue + table_data.append( + { + "": "+", + "Response": key, + "Water Volume Mrm3": f"{qc_vols[key]/1e6:>10.3f}", + "Water Volume Diff": f"{qc_vols[key]/qc_vols['SWATINIT_WVOL']*100:>3.2f} %", + "HC Volume Diff": f"{-qc_vols[key]/qc_vols['SWATINIT_HCVOL']*100:>3.2f} %" + if qc_vols["SWATINIT_HCVOL"] > 0 + else "0.00 %", + } + ) + # Last report the SWAT volumes and change from SWATINIT + table_data.append( + { + "": "=", + "Response": "SWAT", + "Water Volume Mrm3": f"{qc_vols['SWAT_WVOL']/1e6:>10.3f}", + "Water Volume Diff": f"{qc_vols['WVOL_DIFF_PERCENT']:>3.2f} %", + "HC Volume Diff": f"{qc_vols['HCVOL_DIFF_PERCENT']:>3.2f} %", + "HC Volume Mrm3": f"{qc_vols['SWAT_HCVOL']/1e6:>8.3f}", + } + ) + return table_data, [{"name": i, "id": i} for i in column_order] + + +@CACHE.memoize(timeout=CACHE.TIMEOUT) +@webvizstore +def read_csv(csv_file: str) -> pd.DataFrame: + return pd.read_csv(csv_file) diff --git a/webviz_subsurface/plugins/_swatint_qc/views/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/views/__init__.py rename to webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py new file mode 100644 index 000000000..619e4602e --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py @@ -0,0 +1,172 @@ +from turtle import settiltangle +from typing import List + +import webviz_core_components as wcc +from dash import Input, Output, callback, dcc, html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC + +from .._plugin_ids import PlugInIDs + + +class WaterSettings(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + SELECT_QC = "select-qc" + EQLNUM = "eqlnum" + COLOR_BY = "color-by" + MAX_POINTS = "max-points" + QC_FLAG = "qc-flag" + SATNUM = "satnum" + + def __init__(self) -> None: + super().__init__() + + +class Selections(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + SELECT_QC = "select-qc" + EQLNUM = "eqlnum" + COLOR_BY = "color-by" + MAX_POINTS = "max-points" + + def __init__(self, datamodel) -> None: + super().__init__("Selections") + self.datamodel = datamodel + + def layout(self) -> List[Component]: + return [ + wcc.Dropdown( + label="Select QC-visualization:", + id=self.register_component_unique_id(Selections.IDs.SELECT_QC), + options=[ + { + "label": "Waterfall plot for water vol changes", + "value": self.MainPlots.WATERFALL, + }, + { + "label": "Reservoir properties vs Depth", + "value": self.MainPlots.PROP_VS_DEPTH, + }, + ], + value=self.MainPlots.PROP_VS_DEPTH, + clearable=False, + ), + wcc.SelectWithLabel( + label="EQLNUM", + id=self.register_component_unique_id(Selections.IDs.EQLNUM), + options=[ + {"label": ens, "value": ens} for ens in self.datamodel.eqlnums + ], + value=self.datamodel.eqlnums[:1], + size=min(8, len(self.datamodel.eqlnums)), + multi=True, + ), + wcc.Dropdown( + label="Color by", + id=self.register_component_unique_id(Selections.IDs.COLOR_BY), + options=[ + {"label": ens, "value": ens} + for ens in self.datamodel.color_by_selectors + ], + value="QC_FLAG", + clearable=False, + ), + wcc.Label("Max number of points:"), + dcc.Input( + id=self.register_component_unique_id(Selections.IDs.MAX_POINTS), + type="number", + value=5000, + ), + ] + + +""" + def set_callbacks(self) -> None: + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.QC_VIZ), "data"), + Input(self.component_unique_id(Selections.IDs.SELECT_QC), "value"), + ) + def _set_qc_viz(qc_viz: str) -> str: + return qc_viz + + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), "data"), + Input(self.component_unique_id(Selections.IDs.EQLNUM), "value"), + ) + def _set_eqlnum(eqlnum: int) -> int: + return eqlnum + + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.COLOR_BY), "data"), + Input(self.component_unique_id(Selections.IDs.COLOR_BY), "value"), + ) + def _set_color_by(color: str) -> str: + return color + + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.MAX_POINTS), "data"), + Input(self.component_unique_id(Selections.IDs.MAX_POINTS), "value"), + ) + def _set_max_points(max: str) -> str: + return max""" + + +class Filters(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + QC_FLAG = "qc-flag" + SATNUM = "satnum" + RANGE_FILTERS = "range_filters" + + def __init__(self, datamodel) -> None: + super().__init__("Filters") + self.datamodel = datamodel + + def layout(self) -> List[Component]: + return [ + wcc.SelectWithLabel( + label="QC_FLAG", + id=self.register_component_unique_id(Filters.IDs.QC_FLAG), + options=[ + {"label": ens, "value": ens} for ens in self.datamodel.qc_flag + ], + value=self.datamodel.qc_flag, + size=min(8, len(self.datamodel.qc_flag)), + ), + wcc.SelectWithLabel( + label="SATNUM", + id=self.register_component_unique_id(Filters.IDs.SATNUM), + options=[ + {"label": ens, "value": ens} for ens in self.datamodel.satnums + ], + value=self.datamodel.satnums, + size=min(8, len(self.datamodel.satnums)), + ), + self.range_filters( + Filters.IDs.RANGE_FILTERS, + self.datamodel, + ), + ] + + @property + def range_filters(uuid: str, datamodel: SwatinitQcDataModel) -> List: + dframe = datamodel.dframe + filters = [] + for col in datamodel.filters_continuous: + min_val, max_val = dframe[col].min(), dframe[col].max() + filters.append( + wcc.RangeSlider( + label="Depth range" if col == "Z" else col, + id={"id": uuid, "col": col}, + min=min_val, + max=max_val, + value=[min_val, max_val], + marks={ + str(val): {"label": f"{val:.2f}"} for val in [min_val, max_val] + }, + tooltip={"always_visible": False}, + ) + ) + return filters diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/shared_settings/__init__.py rename to webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/shared_settings/_filter.py rename to webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py diff --git a/webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py similarity index 96% rename from webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py rename to webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py index 4ec7db27d..c967eb3d1 100644 --- a/webviz_subsurface/plugins/_swatint_qc/shared_settings/_pick_tab.py +++ b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py @@ -14,7 +14,7 @@ class IDs: TAB_PICKER = "tab-picker" def __init__(self) -> None: - super().__init__() + super().__init__("Pick Tab") self.tab_options = [ {"label": "Overview and Information", "value": "info"}, {"label": "Water Initalization QC plots", "value": "water"}, @@ -36,7 +36,7 @@ def set_callbacks(self) -> None: "data", ), Input( - self.component_unique_id(PickTab.IDs.TAB_PICKER), + self.component_unique_id(PickTab.IDs.TAB_PICKER).to_string(), "value", ), ) diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py new file mode 100644 index 000000000..bc06fbb8e --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py @@ -0,0 +1,3 @@ +from ._dash_table import DashTable +from ._layout_style import LayoutStyle +from ._overview_layout import OverviewViewelement diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_dash_table.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/view_elements/_dash_table.py rename to webviz_subsurface/plugins/_swatinit_qc/view_elements/_dash_table.py diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_layout_style.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/view_elements/_layout_style.py rename to webviz_subsurface/plugins/_swatinit_qc/view_elements/_layout_style.py diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_map_figure.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/view_elements/_map_figure.py rename to webviz_subsurface/plugins/_swatinit_qc/view_elements/_map_figure.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py new file mode 100644 index 000000000..099c94dee --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py @@ -0,0 +1,188 @@ +import webviz_core_components as wcc +from dash import dcc, html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._swatint import SwatinitQcDataModel +from ._dash_table import DashTable +from ._layout_style import LayoutStyle + + +class OverviewViewelement(ViewElementABC): + class IDs: + # pylint: disable=too-few-public-methods + LAYOUT = "layout" + DESCRIBTION = "describtion" + FRAME = "frame" + KEY_NUMBERS = "key-numbers" + BUTTON = "button" + INFO_DIALOG = "info-dialog" + TABLE_LABEL = "tabel-label" + TABLE = "table" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__() + self.datamodel = datamodel + max_pc, min_pc = datamodel.pc_scaling_min_max + wvol, hcvol = datamodel.vol_diff_total + self.number_style = { + "font-weight": "bold", + "font-size": "17px", + "margin-left": "20px", + } + self.infodata = [ + ("HC Volume difference:", f"{hcvol:.2f} %"), + ("Water Volume difference:", f"{wvol:.2f} %"), + ("Maximum Capillary Pressure scaling:", f"{max_pc:.1f}"), + ("Minimum Capillary Pressure scaling:", f"{min_pc:.3g}"), + ] + self.title = "Plugin and 'check_swatinit' information" + + self.tabledata, self.columns = datamodel.table_data_qc_vol_overview() + self.label = ( + "Table showing volume changes from SWATINIT to SWAT at Reservoir conditions" + ) + + def inner_layout(self) -> html.Div: + return html.Div( + id=self.register_component_unique_id(OverviewViewelement.IDs.LAYOUT), + children=[ + html.Div( + style={"height": "40vh", "overflow-y": "auto"}, + children=[ + wcc.FlexBox( + children=[ + wcc.FlexColumn( + [ + self.table, + ], + flex=7, + style={"margin-right": "20px"}, + ), + wcc.FlexColumn(self.infobox, flex=3), + ], + ), + ], + ), + self.describtion, + ], + ) + + @property + def describtion(self) -> html.Div: + return html.Div( + style={"margin-top": "20px"}, + id=OverviewViewelement.IDs.DESCRIBTION, + children=[ + wcc.Header("QC_FLAG descriptions", style=LayoutStyle.HEADER), + dcc.Markdown(qc_flag_description()), + ], + ) + + @property + def infobox(self) -> Component: + return wcc.Frame( + style={"height": "90%"}, + id=self.register_component_unique_id(OverviewViewelement.IDs.FRAME), + children=[ + wcc.Header("Information", style=LayoutStyle.HEADER), + self.info_dialog, + wcc.Header("Key numbers", style=LayoutStyle.HEADER), + html.Div( + [ + html.Div([text, html.Span(num, style=self.number_style)]) + for text, num in self.infodata + ], + id=self.register_component_unique_id( + OverviewViewelement.IDs.KEY_NUMBERS + ), + ), + ], + ) + + @property + def info_dialog(self) -> html.Div: + return html.Div( + style={"margin-bottom": "30px"}, + children=[ + html.Button( + "CLICK HERE FOR INFORMATION", # var egt samme som tittelen til wcc.Dialog + style={"width": "100%", "background-color": "white"}, + id=self.register_component_unique_id( + OverviewViewelement.IDs.BUTTON + ), + ), + wcc.Dialog( + title=self.title, + id=self.register_component_unique_id( + OverviewViewelement.IDs.INFO_DIALOG + ), + max_width="md", + open=False, + children=dcc.Markdown(check_swatinit_description()), + ), + ], + ) + + @property + def table(self) -> html.Div: + return html.Div( + children=[ + html.Div( + html.Label(self.label, className="webviz-underlined-label"), + style={"margin-bottom": "20px"}, + id=self.register_component_unique_id( + OverviewViewelement.IDs.TABLE_LABEL + ), + ), + DashTable( + id=self.register_component_unique_id(OverviewViewelement.IDs.TABLE), + data=self.tabledata, + columns=self.columns, + style_data_conditional=[ + { + "if": {"row_index": [0, len(self.tabledata) - 1]}, + **LayoutStyle.TABLE_HIGHLIGHT, + }, + ], + ), + ], + ) + + +def qc_flag_description() -> str: # listen på bunnen av første side + return """ +- **PC_SCALED** - Capillary pressure have been scaled and SWATINIT was accepted. +- **FINE_EQUIL** - If item 9 in EQUIL is nonzero then initialization happens in a vertically + refined model. Capillary pressure is still scaled, but water might be added or lost. +- **SWL_TRUNC** - If SWL is larger than SWATINIT, SWAT will be reset to SWL. Extra water is + added and hydrocarbons are lost. +- **SWATINIT_1** - When SWATINIT is 1 above the contact, Eclipse will ignore SWATINIT and not + touch the capillary pressure function which typically results in extra hydrocarbons. + This could be ok as long as the porosities and/or permeabilities of these cells are small. + If SWU is included, cells where SWATINIT is equal or larger than SWU will + also be flagged as SWATINIT_1 +- **HC_BELOW_FWL** - If SWATINIT is less than 1 below the contact provided in EQUIL, Eclipse will + ignore it and not scale the capillary pressure function. SWAT will be 1, unless a capillary + pressure function with negative values is in SWOF/SWFN. This results in the loss of HC volumes. +- **PPCWMAX** - If an upper limit of how much capillary pressure scaling is allowed is set, water will be + lost if this limit is hit. +- **WATER** - SWATINIT was 1 in the water zone, and SWAT is set to 1. +> **Consult the [check_swatinit](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) + documentation for more detailed descriptions** +""" + + +def check_swatinit_description() -> str: + return """ +This plugin is used to visualize the output from **check_swatinit** which is a **QC tool for Water Initialization in Eclipse runs +when the SWATINIT keyword has been used**. It is used to quantify how much the volume changes from **SWATINIT** to **SWAT** at time +zero in the dynamical model, and help understand why it changes. +When the keyword SWATINIT has been used as water initialization option in Eclipse, capillary pressure scaling on a cell-by-cell basis will +occur in order to match SWATINIT from the geomodel. +This process has some exceptions which can cause volume changes from SWATINIT to SWAT at time zero. +Each cell in the dynamical model has been flagged according to what has happened during initialization, and information is stored +in the **QC_FLAG** column. +> Check the maximum capillary pressure pr SATNUM in each EQLNUM to ensure extreme values were not necessary +[check_swatinit documentation](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) +""" diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_properties_vs_depth.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/view_elements/_properties_vs_depth.py rename to webviz_subsurface/plugins/_swatinit_qc/view_elements/_properties_vs_depth.py diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/view_elements/_waterfall_plot.py rename to webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py new file mode 100644 index 000000000..98c6f3689 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py @@ -0,0 +1 @@ +from ._overview_info import OverviewTabLayout diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py similarity index 100% rename from webviz_subsurface/plugins/_swatint_qc/views/_capilary_preassure.py rename to webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py new file mode 100644 index 000000000..003145058 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py @@ -0,0 +1,46 @@ +import webviz_core_components as wcc +from dash import Input, Output, State, callback, html +from dash.exceptions import PreventUpdate +from webviz_config.webviz_plugin_subclasses import ViewABC + +from .._plugin_ids import PlugInIDs +from .._swatint import SwatinitQcDataModel +from ..view_elements import OverviewViewelement + + +class OverviewTabLayout(ViewABC): + class IDs: + # pylint: disable=too-few-public-methods + OVERVIEW_TAB = "overview-tab" + MAIN_CLOUMN = "main-column" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__("Overview and Information") + self.datamodel = datamodel + + main_column = self.add_column(OverviewTabLayout.IDs.MAIN_CLOUMN) + row = main_column.make_row() + row.add_view_element( + OverviewViewelement(self.datamodel), OverviewTabLayout.IDs.OVERVIEW_TAB + ) + + def set_callbacks(self) -> None: + @callback( + Output( + self.view_element(OverviewTabLayout.IDs.OVERVIEW_TAB) + .component_unique_id(OverviewViewelement.IDs.INFO_DIALOG) + .to_string(), # litt usikker på denne IDen her? + "open", + ), + Input(self.get_store_unique_id(PlugInIDs.Stores.Overview.BUTTON), "data"), + State( + self.view_element(OverviewTabLayout.IDs.OVERVIEW_TAB) + .component_unique_id(OverviewViewelement.IDs.INFO_DIALOG) + .to_string(), + "open", + ), + ) + def open_close_information_dialog(_n_click: list, is_open: bool) -> bool: + if _n_click is not None: + return not is_open + raise PreventUpdate diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py similarity index 85% rename from webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py rename to webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py index 00b3943c2..f62905332 100644 --- a/webviz_subsurface/plugins/_swatint_qc/views/_water_initialization.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py @@ -1,12 +1,43 @@ - - class TabQqPlotLayout: - class MainPlots(str, Enum): - WATERFALL = auto() - PROP_VS_DEPTH = auto() +from typing import Dict, List, Optional, Tuple, Union - def __init__(self, get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: +from typing import Any, Callable, List, Optional + +import pandas as pd +import plotly.graph_objects as go +import webviz_core_components as wcc +from dash import dash_table, dcc, html + +import pandas as pd +from dash import ( + ClientsideFunction, + Input, + Output, + State, + callback, + callback_context, + clientside_callback, +) +from dash.exceptions import PreventUpdate +from webviz_config import WebvizSettings +from webviz_config.webviz_assets import WEBVIZ_ASSETS +from webviz_config.webviz_plugin_subclasses import ViewABC + +import webviz_subsurface +from webviz_subsurface._components.tornado._tornado_bar_chart import TornadoBarChart +from webviz_subsurface._components.tornado._tornado_data import TornadoData +from webviz_subsurface._components.tornado._tornado_table import TornadoTable + +from .._plugin_ids import PlugInIDs + + +class TabQqPlotLayout(ViewABC): + class IDs: + WATERFALL = "waterfall" + PROP_VS_DEPTH = "prop-vs-depth" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__() self.datamodel = datamodel - self.get_uuid = get_uuid def main_layout( self, diff --git a/webviz_subsurface/plugins/_swatint_qc/__init__.py b/webviz_subsurface/plugins/_swatint_qc/__init__.py deleted file mode 100644 index 262986762..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._plugin import SwatinitQC diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py deleted file mode 100644 index 4701c4e52..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ._dash_table import DashTable -from ._layout_style import LayoutStyle -from .overview_elements import Describtion, InfoBox, InfoDialog, OverViewTable diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py deleted file mode 100644 index b5bda40c8..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ._info_box import InfoBox -from ._info_dialog import InfoDialog -from ._overview_table import OverviewTable -from .describtion import Describtion diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py deleted file mode 100644 index 495c2b670..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_describtion.py +++ /dev/null @@ -1,48 +0,0 @@ -import webviz_core_components as wcc -from dash import dcc, html -from webviz_config.webviz_plugin_subclasses import ViewElementABC - -from .._layout_style import LayoutStyle - - -class Describtion(ViewElementABC): - class IDs: - TEXT = "text" - - def __init__( - self, - ) -> None: - super().__init__() - - def inner_layout(self) -> html.Div: - return html.Div( - style={"margin-top": "20px"}, - id=Describtion.IDs.TEXT, - children=[ - wcc.Header("QC_FLAG descriptions", style=LayoutStyle.HEADER), - dcc.Markdown(qc_flag_description()), - ], - ) - - -def qc_flag_description() -> str: # listen på bunnen av første side - return """ -- **PC_SCALED** - Capillary pressure have been scaled and SWATINIT was accepted. -- **FINE_EQUIL** - If item 9 in EQUIL is nonzero then initialization happens in a vertically - refined model. Capillary pressure is still scaled, but water might be added or lost. -- **SWL_TRUNC** - If SWL is larger than SWATINIT, SWAT will be reset to SWL. Extra water is - added and hydrocarbons are lost. -- **SWATINIT_1** - When SWATINIT is 1 above the contact, Eclipse will ignore SWATINIT and not - touch the capillary pressure function which typically results in extra hydrocarbons. - This could be ok as long as the porosities and/or permeabilities of these cells are small. - If SWU is included, cells where SWATINIT is equal or larger than SWU will - also be flagged as SWATINIT_1 -- **HC_BELOW_FWL** - If SWATINIT is less than 1 below the contact provided in EQUIL, Eclipse will - ignore it and not scale the capillary pressure function. SWAT will be 1, unless a capillary - pressure function with negative values is in SWOF/SWFN. This results in the loss of HC volumes. -- **PPCWMAX** - If an upper limit of how much capillary pressure scaling is allowed is set, water will be - lost if this limit is hit. -- **WATER** - SWATINIT was 1 in the water zone, and SWAT is set to 1. -> **Consult the [check_swatinit](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) - documentation for more detailed descriptions** -""" diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py deleted file mode 100644 index dab41ed79..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_box.py +++ /dev/null @@ -1,48 +0,0 @@ -import webviz_core_components as wcc -from dash import html -from dash.development.base_component import Component -from webviz_config.webviz_plugin_subclasses import ViewElementABC - -from .._layout_style import LayoutStyle -from ._info_dialog import InfoDialog - - -class InfoBox(ViewElementABC): - class IDs: - # pylint disable=too-few-public-methods - FRAME = "frame" - KEY_NUMBERS = "key-numbers" - - def __init__(self, datamodel) -> None: - super().__init__() - max_pc, min_pc = datamodel.pc_scaling_min_max - wvol, hcvol = datamodel.vol_diff_total - self.number_style = { - "font-weight": "bold", - "font-size": "17px", - "margin-left": "20px", - } - self.data = [ - ("HC Volume difference:", f"{hcvol:.2f} %"), - ("Water Volume difference:", f"{wvol:.2f} %"), - ("Maximum Capillary Pressure scaling:", f"{max_pc:.1f}"), - ("Minimum Capillary Pressure scaling:", f"{min_pc:.3g}"), - ] - - def inner_layout(self) -> Component: - return wcc.Frame( - style={"height": "90%"}, - id=self.register_component_unique_id(InfoBox.IDs.FRAME), - children=[ - wcc.Header("Information", style=LayoutStyle.HEADER), - InfoDialog(), - wcc.Header("Key numbers", style=LayoutStyle.HEADER), - html.Div( - [ - html.Div([text, html.Span(num, style=self.number_style)]) - for text, num in self.data - ], - id=self.register_component_unique_id(InfoBox.IDs.KEY_NUMBERS), - ), - ], - ) diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py deleted file mode 100644 index 792ad720f..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_info_dialog.py +++ /dev/null @@ -1,50 +0,0 @@ -import webviz_core_components as wcc -from dash import dcc, html -from webviz_config.webviz_plugin_subclasses import ViewElementABC - - -class InfoDialog(ViewElementABC): - class IDs: - # pylint:diable=too-few-public-methods - BUTTON = "button" - INFO_DIALOG = "info-dialog" - - def __init__( - self, - ) -> None: - super().__init__() - self.title = "Plugin and 'check_swatinit' information" - - def inner_layout(self) -> html.Div: - return html.Div( - style={"margin-bottom": "30px"}, - children=[ - html.Button( - "CLICK HERE FOR INFORMATION", # var egt samme som tittelen til wcc.Dialog - style={"width": "100%", "background-color": "white"}, - id=self.register_component_unique_id(InfoDialog.IDs.BUTTON), - ), - wcc.Dialog( - title=self.title, - id=self.register_component_unique_id(InfoDialog.IDs.INFO_DIALOG), - max_width="md", - open=False, - children=dcc.Markdown(check_swatinit_description()), - ), - ], - ) - - -def check_swatinit_description() -> str: - return """ -This plugin is used to visualize the output from **check_swatinit** which is a **QC tool for Water Initialization in Eclipse runs -when the SWATINIT keyword has been used**. It is used to quantify how much the volume changes from **SWATINIT** to **SWAT** at time -zero in the dynamical model, and help understand why it changes. -When the keyword SWATINIT has been used as water initialization option in Eclipse, capillary pressure scaling on a cell-by-cell basis will -occur in order to match SWATINIT from the geomodel. -This process has some exceptions which can cause volume changes from SWATINIT to SWAT at time zero. -Each cell in the dynamical model has been flagged according to what has happened during initialization, and information is stored -in the **QC_FLAG** column. -> Check the maximum capillary pressure pr SATNUM in each EQLNUM to ensure extreme values were not necessary -[check_swatinit documentation](https://fmu-docs.equinor.com/docs/subscript/scripts/check_swatinit.html) -""" diff --git a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py b/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py deleted file mode 100644 index bdc1823e6..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/view_elements/overview_elements/_overview_table.py +++ /dev/null @@ -1,38 +0,0 @@ -from dash import html -from webviz_config.webviz_plugin_subclasses import ViewElementABC - -from .._dash_table import DashTable -from .._layout_style import LayoutStyle - - -class OverviewTable(ViewElementABC): - class IDs: - LABEL = "label" - TABLE = "table" - - def __init__(self, datamodel) -> None: - super().__init__() - self.data, self.columns = datamodel.table_data_qc_vol_overview() - self.label = ( - "Table showing volume changes from SWATINIT to SWAT at Reservoir conditions" - ) - - def inner_layout(self) -> html.Div: - return html.Div( - children=[ - html.Div( - html.Label(self.label, className="webviz-underlined-label"), - style={"margin-bottom": "20px"}, - ), - DashTable( - data=self.data, - columns=self.columns, - style_data_conditional=[ - { - "if": {"row_index": [0, len(self.data) - 1]}, - **LayoutStyle.TABLE_HIGHLIGHT, - }, - ], - ), - ], - ) diff --git a/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py deleted file mode 100644 index 433ad7b24..000000000 --- a/webviz_subsurface/plugins/_swatint_qc/views/_overview_info.py +++ /dev/null @@ -1,65 +0,0 @@ -from pathlib import Path -from subprocess import call -from typing import Dict, List, Optional, Tuple, Union - -import pandas as pd -import webviz_core_components as wcc -from dash import ( - Input, - Output, - State, - callback, - callback_context, - clientside_callback, - dcc, - html, -) -from pyparsing import DebugExceptionAction, col -from webviz_config.webviz_plugin_subclasses import ViewABC - -from .._plugin_ids import PlugInIDs -from ..view_elements import Describtion, InfoBox, InfoDialog, OverViewTable - - -class OverviewTabLayout(ViewABC): - class IDs: - # pylint: disable=too-few-public-methods - TABLE = "table" - INFO_BOX = "info-box" - INFO_DIALOG = "info-dialog" - DESCRIBTION = "describtion" - MAIN_CLOUMN = "main-column" - - def __init__(self, datamodel: SwatinitQcDataModel) -> None: - super().__init__() - self.datamodel = datamodel - - @property - def main_layout(self) -> html.Div: # burde det være noen IDs her? - return html.Div( - children=[ - html.Div( - style={"height": "40vh", "overflow-y": "auto"}, - children=[ - wcc.FlexBox( - children=[ - wcc.FlexColumn( - [ - OverViewTable(self.datamodel), - ], - flex=7, - style={"margin-right": "20px"}, - ), - wcc.FlexColumn(InfoBox(self.datamodel), flex=3), - ], - ), - ], - ), - Describtion(), - ] - ) - - def set_callbacks(self) -> None: - @callback( - - ) From 31cf92e2924e2ac81cbe12cb63486adadbfe9496 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Tue, 2 Aug 2022 15:57:20 +0200 Subject: [PATCH 4/7] saving changes --- .../plugins/_swatinit_qc/_plugin.py | 41 +++- .../plugins/_swatinit_qc/_plugin_ids.py | 22 +- .../_swatinit_qc/settings_groups/__init__.py | 2 + .../settings_groups/_caplilar_settings.py | 119 ++++++++++ .../settings_groups/_water_settings.py | 81 +++---- .../_swatinit_qc/shared_settings/_filter.py | 57 ----- .../_swatinit_qc/view_elements/__init__.py | 3 + .../view_elements/_capilar_layout.py | 112 ++++++++++ .../_swatinit_qc/view_elements/_fullscreen.py | 8 + .../view_elements/_overview_layout.py | 7 +- .../view_elements/_water_layout.py | 95 ++++++++ .../view_elements/_waterfall_plot.py | 4 - .../plugins/_swatinit_qc/views/__init__.py | 2 + .../_swatinit_qc/views/_capilary_preassure.py | 172 +++------------ .../_swatinit_qc/views/_overview_info.py | 2 +- .../views/_water_initialization.py | 207 +++--------------- 16 files changed, 491 insertions(+), 443 deletions(-) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py delete mode 100644 webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py create mode 100644 webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py create mode 100644 webviz_subsurface/plugins/_swatinit_qc/view_elements/_fullscreen.py create mode 100644 webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py index 2fe4a2c86..e707e0f60 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py @@ -1,17 +1,14 @@ from pathlib import Path -from typing import Callable, List, Optional, Tuple +from typing import Optional import webviz_core_components as wcc from webviz_config import WebvizPluginABC, WebvizSettings -from webviz_subsurface._datainput.fmu_input import find_sens_type -from webviz_subsurface._providers import EnsembleTableProviderFactory - from ._error import error from ._plugin_ids import PlugInIDs from ._swatint import SwatinitQcDataModel from .shared_settings import PickTab -from .views import OverviewTabLayout +from .views import OverviewTabLayout, TabMaxPcInfoLayout, TabQqPlotLayout class SwatinitQC(WebvizPluginABC): @@ -39,14 +36,44 @@ def __init__( ) self.add_shared_settings_group(PickTab(), PlugInIDs.SharedSettings.PICK_VIEW) + # Stores used in Overview tab self.add_store( PlugInIDs.Stores.Overview.BUTTON, WebvizPluginABC.StorageType.SESSION ) + # Stores used in Water tab + self.add_store( + PlugInIDs.Stores.Water.QC_VIZ, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PlugInIDs.Stores.Water.EQLNUM, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PlugInIDs.Stores.Water.COLOR_BY, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PlugInIDs.Stores.Water.MAX_POINTS, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PlugInIDs.Stores.Water.QC_FLAG, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PlugInIDs.Stores.Water.SATNUM, WebvizPluginABC.StorageType.SESSION + ) + + # Stores used in Capilaty tab + self.add_store( + PlugInIDs.Stores.Capilary.SPLIT_TABLE_BY, + WebvizPluginABC.StorageType.SESSION, + ) + self.add_store( + PlugInIDs.Stores.Capilary.MAX_PC_SCALE, WebvizPluginABC.StorageType.SESSION + ) + self.add_view( OverviewTabLayout(self._datamodel), - PlugInIDs.ViewGroups.Overview.OVERVIEW_TAB, - PlugInIDs.ViewGroups.Overview.GROUP_NAME, + PlugInIDs.SwatinitViews.OVERVIEW, + PlugInIDs.SwatinitViews.GROUP_NAME, ) @property diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py index eacc146d1..e702280a5 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py @@ -11,7 +11,6 @@ class Stores: class Shared: # pylint: disable=too-few-public-methods PICK_VIEW = "pick-view" - EQLNUM = "eqlnum" class Overview: # pylint: disable=too-few-public-methods @@ -24,7 +23,7 @@ class Water: COLOR_BY = "color_by" MAX_POINTS = "max-points" QC_FLAG = "qc-flag" - STANUM = "satnum" + SATNUM = "satnum" class Capilary: # pylint: disable=too-few-public-methods @@ -47,19 +46,10 @@ class QcFlags: UNKNOWN = "UNKNOWN" WATER = "WATER" - class ViewGroups: + class SwatinitViews: # pylint: disable=too-few-public-methods - class Overview: - # pylint: disable=too-few-public-methods - OVERVIEW_TAB = "overview-tab" - GROUP_NAME = "overview-group" + GROUP_NAME = "swatinit-group" - class Water: - # pylint: disable=too-few-public-methods - WATER_TAB = "water-tab" - GROUP_NAME = "group-name" - - class Capilar: - # pylint: disable=too-few-public-methods - CAPILAR_TAB = "capilar-tab" - GROUP_NAEME = "group-name" + OVERVIEW = "overview" + WATER = "water" + CAPILAR = "capilar" diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py index e69de29bb..3db3ef449 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py @@ -0,0 +1,2 @@ +from ._capilar_settings import CapilarFilters, CapilarSelections +from ._water_settings import WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py new file mode 100644 index 000000000..a38c85cec --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py @@ -0,0 +1,119 @@ +from typing import List + +import webviz_core_components as wcc +from dash import Input, Output, callback, dcc +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC + +from .._plugin_ids import PlugInIDs +from .._swatint import SwatinitQcDataModel + + +class CapilarSelections(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + SPLIT_TABLE_BY = "split-table-by" + MAX_THRESH = "max-thresh" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__("Selections") + self.datamodel = datamodel + + def layout(self) -> List[Component]: + return [ + wcc.RadioItems( + label="Split table by:", + id=self.register_component_unique_id( + CapilarSelections.IDs.SPLIT_TABLE_BY + ), + options=[ + {"label": "SATNUM", "value": "SATNUM"}, + {"label": "SATNUM and EQLNUM", "value": "both"}, + ], + value="SATNUM", + ), + wcc.Label("Maximum PC_SCALING threshold"), + dcc.Input( + id=self.register_component_unique_id(CapilarSelections.IDs.MAX_THRESH), + type="number", + persistence=True, + persistence_type="session", + ), + ] + + def set_callbacks(self) -> None: + @callback( + Output( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.SPLIT_TABLE_BY), + "data", + ), + Input( + self.component_unique_id( + CapilarSelections.IDs.SPLIT_TABLE_BY + ).to_string(), + "value", + ), + ) + def _set_split(split: str) -> str: + return split + + @callback( + Output( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.MAX_PC_SCALE), "data" + ), + Input( + self.component_unique_id(CapilarSelections.IDs.MAX_THRESH).to_string(), + "value", + ), + ) + def _set_max_pc(max_pc: int) -> int: + return max_pc + + +class CapilarFilters(SettingsGroupABC): + class IDs: + # pylint: disable=too-few-public-methods + EQLNUM = "eqlnum" + RANGE_FILTERS = "range_filters" + + def __init__(self, datamodel: SwatinitQcDataModel) -> None: + super().__init__("Filter") + self.datamodel = datamodel + + def layout(self) -> List[Component]: + return [ + wcc.SelectWithLabel( + label="EQLNUM", + id=self.register_component_unique_id(CapilarFilters.IDs.EQLNUM), + options=[ + {"label": ens, "value": ens} for ens in self.datamodel.eqlnums + ], + value=self.datamodel.eqlnums[:1], + size=min(8, len(self.datamodel.eqlnums)), + multi=True, + ), + self.range_filters( + CapilarFilters.IDs.RANGE_FILTERS, + ), + ] + + @property + def range_filters(self, uuid: str) -> List: + dframe = self.datamodel.dframe + filters = [] + for col in self.datamodel.filters_continuous: + min_val, max_val = dframe[col].min(), dframe[col].max() + filters.append( + wcc.RangeSlider( + label="Depth range" if col == "Z" else col, + id={"id": uuid, "col": col}, + min=min_val, + max=max_val, + value=[min_val, max_val], + marks={ + str(val): {"label": f"{val:.2f}"} for val in [min_val, max_val] + }, + tooltip={"always_visible": False}, + ) + ) + return filters diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py index 619e4602e..ee631bbea 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py @@ -1,37 +1,25 @@ -from turtle import settiltangle from typing import List import webviz_core_components as wcc -from dash import Input, Output, callback, dcc, html +from dash import Input, Output, callback, dcc from dash.development.base_component import Component from webviz_config.webviz_plugin_subclasses import SettingsGroupABC from .._plugin_ids import PlugInIDs +from .._swatint import SwatinitQcDataModel -class WaterSettings(SettingsGroupABC): - class IDs: - # pylint: disable=too-few-public-methods - SELECT_QC = "select-qc" - EQLNUM = "eqlnum" - COLOR_BY = "color-by" - MAX_POINTS = "max-points" - QC_FLAG = "qc-flag" - SATNUM = "satnum" - - def __init__(self) -> None: - super().__init__() - - -class Selections(SettingsGroupABC): +class WaterSelections(SettingsGroupABC): class IDs: # pylint: disable=too-few-public-methods + WATERFALL = "waterfall" + PROP_VS_DEPTH = "prop-vs-depth" SELECT_QC = "select-qc" EQLNUM = "eqlnum" COLOR_BY = "color-by" MAX_POINTS = "max-points" - def __init__(self, datamodel) -> None: + def __init__(self, datamodel: SwatinitQcDataModel) -> None: super().__init__("Selections") self.datamodel = datamodel @@ -39,23 +27,23 @@ def layout(self) -> List[Component]: return [ wcc.Dropdown( label="Select QC-visualization:", - id=self.register_component_unique_id(Selections.IDs.SELECT_QC), + id=self.register_component_unique_id(WaterSelections.IDs.SELECT_QC), options=[ { "label": "Waterfall plot for water vol changes", - "value": self.MainPlots.WATERFALL, + "value": WaterSelections.IDs.WATERFALL, }, { "label": "Reservoir properties vs Depth", - "value": self.MainPlots.PROP_VS_DEPTH, + "value": WaterSelections.IDs.PROP_VS_DEPTH, }, ], - value=self.MainPlots.PROP_VS_DEPTH, + value=WaterSelections.IDs.PROP_VS_DEPTH, clearable=False, ), wcc.SelectWithLabel( label="EQLNUM", - id=self.register_component_unique_id(Selections.IDs.EQLNUM), + id=self.register_component_unique_id(WaterSelections.IDs.EQLNUM), options=[ {"label": ens, "value": ens} for ens in self.datamodel.eqlnums ], @@ -65,7 +53,7 @@ def layout(self) -> List[Component]: ), wcc.Dropdown( label="Color by", - id=self.register_component_unique_id(Selections.IDs.COLOR_BY), + id=self.register_component_unique_id(WaterSelections.IDs.COLOR_BY), options=[ {"label": ens, "value": ens} for ens in self.datamodel.color_by_selectors @@ -75,45 +63,43 @@ def layout(self) -> List[Component]: ), wcc.Label("Max number of points:"), dcc.Input( - id=self.register_component_unique_id(Selections.IDs.MAX_POINTS), + id=self.register_component_unique_id(WaterSelections.IDs.MAX_POINTS), type="number", value=5000, ), ] - -""" def set_callbacks(self) -> None: @callback( Output(self.get_store_unique_id(PlugInIDs.Stores.Water.QC_VIZ), "data"), - Input(self.component_unique_id(Selections.IDs.SELECT_QC), "value"), + Input(self.component_unique_id(WaterSelections.IDs.SELECT_QC), "value"), ) def _set_qc_viz(qc_viz: str) -> str: return qc_viz @callback( Output(self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), "data"), - Input(self.component_unique_id(Selections.IDs.EQLNUM), "value"), + Input(self.component_unique_id(WaterSelections.IDs.EQLNUM), "value"), ) def _set_eqlnum(eqlnum: int) -> int: return eqlnum @callback( Output(self.get_store_unique_id(PlugInIDs.Stores.Water.COLOR_BY), "data"), - Input(self.component_unique_id(Selections.IDs.COLOR_BY), "value"), + Input(self.component_unique_id(WaterSelections.IDs.COLOR_BY), "value"), ) def _set_color_by(color: str) -> str: return color @callback( Output(self.get_store_unique_id(PlugInIDs.Stores.Water.MAX_POINTS), "data"), - Input(self.component_unique_id(Selections.IDs.MAX_POINTS), "value"), + Input(self.component_unique_id(WaterSelections.IDs.MAX_POINTS), "value"), ) def _set_max_points(max: str) -> str: - return max""" + return max -class Filters(SettingsGroupABC): +class WaterFilters(SettingsGroupABC): class IDs: # pylint: disable=too-few-public-methods QC_FLAG = "qc-flag" @@ -128,7 +114,7 @@ def layout(self) -> List[Component]: return [ wcc.SelectWithLabel( label="QC_FLAG", - id=self.register_component_unique_id(Filters.IDs.QC_FLAG), + id=self.register_component_unique_id(WaterFilters.IDs.QC_FLAG), options=[ {"label": ens, "value": ens} for ens in self.datamodel.qc_flag ], @@ -137,7 +123,7 @@ def layout(self) -> List[Component]: ), wcc.SelectWithLabel( label="SATNUM", - id=self.register_component_unique_id(Filters.IDs.SATNUM), + id=self.register_component_unique_id(WaterFilters.IDs.SATNUM), options=[ {"label": ens, "value": ens} for ens in self.datamodel.satnums ], @@ -145,16 +131,16 @@ def layout(self) -> List[Component]: size=min(8, len(self.datamodel.satnums)), ), self.range_filters( - Filters.IDs.RANGE_FILTERS, + WaterFilters.IDs.RANGE_FILTERS, self.datamodel, ), ] @property - def range_filters(uuid: str, datamodel: SwatinitQcDataModel) -> List: - dframe = datamodel.dframe + def range_filters(self, uuid: str) -> List: + dframe = self.datamodel.dframe filters = [] - for col in datamodel.filters_continuous: + for col in self.datamodel.filters_continuous: min_val, max_val = dframe[col].min(), dframe[col].max() filters.append( wcc.RangeSlider( @@ -170,3 +156,20 @@ def range_filters(uuid: str, datamodel: SwatinitQcDataModel) -> List: ) ) return filters + + def set_callbacks(self) -> None: + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.QC_FLAG), "data"), + Input( + self.component_unique_id(WaterFilters.IDs.QC_FLAG).to_string(), "value" + ), + ) + def _set_qc_flag(qc_flag: List[str]) -> List[str]: + return qc_flag + + @callback( + Output(self.get_store_unique_id(PlugInIDs.Stores.Water.SATNUM), "data"), + Input(self.component_unique_id(WaterFilters.IDs.SATNUM), "value"), + ) + def _set_satnum(satnum: List[int]) -> List[int]: + return satnum diff --git a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py deleted file mode 100644 index e6b2c894c..000000000 --- a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_filter.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import List - -import webviz_core_components as wcc -from dash import Input, Output, callback, html -from dash.development.base_component import Component -from webviz_config.webviz_plugin_subclasses import SettingsGroupABC - - -class Filters(SettingsGroupABC): - class IDs: - # pylint: disable=too-few-public-methods - EQLNUM = "eqlnum" - - def __init__( - self, - datamodel: SwatinitQcDataModel, - ) -> None: - super().__init__("Filters") - self.datamodel = datamodel - - @property - def layout(self) -> List[Component]: - elements = [ - wcc.SelectWithLabel( - label="EQLNUM", - id=self.register_component_unique_id(Filters.IDs.EQLNUM), - options=[ - {"label": ens, "value": ens} for ens in self.datamodel.eqlnums - ], - value=self.datamodel.eqlnums[:1], - size=min(8, len(self.datamodel.eqlnums)), - multi=True, - ), - ] - elements.append(self.range_filters) - return elements - - @property - def range_filters(uuid: str, datamodel: SwatinitQcDataModel) -> List: - dframe = datamodel.dframe - filters = [] - for col in datamodel.filters_continuous: - min_val, max_val = dframe[col].min(), dframe[col].max() - filters.append( - wcc.RangeSlider( - label="Depth range" if col == "Z" else col, - id={"id": uuid, "col": col}, - min=min_val, - max=max_val, - value=[min_val, max_val], - marks={ - str(val): {"label": f"{val:.2f}"} for val in [min_val, max_val] - }, - tooltip={"always_visible": False}, - ) - ) - return filters diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py index bc06fbb8e..c254604af 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py @@ -1,3 +1,6 @@ +from ._capilar_layout import CapilarViewelement from ._dash_table import DashTable +from ._fullscreen import FullScreen from ._layout_style import LayoutStyle from ._overview_layout import OverviewViewelement +from ._water_layout import WaterViewelement diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py new file mode 100644 index 000000000..2ac9c324a --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py @@ -0,0 +1,112 @@ +from typing import List + +import pandas as pd +import plotly.graph_objects as go +import webviz_core_components as wcc +from dash import dash_table, dcc +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._swatint import SwatinitQcDataModel +from ._dash_table import DashTable +from ._fullscreen import FullScreen +from ._layout_style import LayoutStyle + + +class CapilarViewelement(ViewElementABC): + """All elements visible in the 'Caplillary pressure scaling'-tab + gathered in one viewelement""" + + class IDs: + # pylint: disable=too-few-public-methods + INFO_TEXT = "info-text" + MAP = "map" + TABLE = "table" + + def __init__( + self, + datamodel: SwatinitQcDataModel, + dframe: pd.DataFrame, + selectors: list, + map_figure: go.Figure, + ) -> None: + super().__init__() + self.datamodel = datamodel + self.dframe = dframe + self.selectors = selectors + self.map_figure = map_figure + + def inner_layout(self) -> List[Component]: + return [ + wcc.Header("Maximum capillary pressure scaling", style=LayoutStyle.HEADER), + wcc.FlexBox( + style={"margin-top": "10px", "height": "40vh"}, + children=[ + wcc.FlexColumn( + dcc.Markdown(pc_columns_description()), + id=self.register_component_unique_id( + CapilarViewelement.IDs.INFO_TEXT + ), + flex=7, + style={"margin-right": "40px"}, + ), + wcc.FlexColumn( + FullScreen( + wcc.Graph( + style={"height": "100%", "min-height": "35vh"}, + figure=self.map_figure, + id=self.register_component_unique_id( + CapilarViewelement.IDs.MAP + ), + ) + ), + flex=3, + ), + ], + ), + self.max_pc_table(text_columns=self.selectors), + ] + + @property + def max_pc_table(self, text_columns: list) -> dash_table: + return DashTable( + data=self.dframe.to_dict("records"), + columns=[ + { + "name": i, + "id": i, + "type": "numeric" if i not in text_columns else "text", + "format": {"specifier": ".4~r"} if i not in text_columns else {}, + } + for i in self.dframe.columns + ], + height="48vh", + sort_action="native", + fixed_rows={"headers": True}, + style_cell={ + "minWidth": LayoutStyle.TABLE_CELL_WIDTH, + "maxWidth": LayoutStyle.TABLE_CELL_WIDTH, + "width": LayoutStyle.TABLE_CELL_WIDTH, + }, + style_data_conditional=[ + { + "if": { + "filter_query": f"{{{self.datamodel.COLNAME_THRESHOLD}}} > 0", + }, + **LayoutStyle.TABLE_HIGHLIGHT, + }, + ], + ) + + +def pc_columns_description() -> str: + return f""" +> **Column descriptions** +> - **PCOW_MAX** - Maximum capillary pressure from the input SWOF/SWFN tables +> - **PC_SCALING** - Maximum capillary pressure scaling applied +> - **PPCW** - Maximum capillary pressure after scaling +> - **{SwatinitQcDataModel.COLNAME_THRESHOLD}** - Column showing how many percent of the pc-scaled dataset that match the user-selected threshold +*PPCW = PCOW_MAX \* PC_SCALING* +A threshold for the maximum capillary scaling can be set in the menu. +The table will show how many percent of the dataset that exceeds this value, and cells above the threshold will be shown in the map ➡️ +""" diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_fullscreen.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_fullscreen.py new file mode 100644 index 000000000..8ea588122 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_fullscreen.py @@ -0,0 +1,8 @@ +from typing import Any, List + +import webviz_core_components as wcc + + +class FullScreen(wcc.WebvizPluginPlaceholder): + def __init__(self, children: List[Any]) -> None: + super().__init__(buttons=["expand"], children=children) diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py index 099c94dee..af6c8ccf1 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py @@ -9,6 +9,9 @@ class OverviewViewelement(ViewElementABC): + """All elements visible in the 'Overview and Information'-tab + gathered in one viewelement""" + class IDs: # pylint: disable=too-few-public-methods LAYOUT = "layout" @@ -106,7 +109,7 @@ def info_dialog(self) -> html.Div: style={"margin-bottom": "30px"}, children=[ html.Button( - "CLICK HERE FOR INFORMATION", # var egt samme som tittelen til wcc.Dialog + "CLICK HERE FOR INFORMATION", style={"width": "100%", "background-color": "white"}, id=self.register_component_unique_id( OverviewViewelement.IDs.BUTTON @@ -150,7 +153,7 @@ def table(self) -> html.Div: ) -def qc_flag_description() -> str: # listen på bunnen av første side +def qc_flag_description() -> str: return """ - **PC_SCALED** - Capillary pressure have been scaled and SWATINIT was accepted. - **FINE_EQUIL** - If item 9 in EQUIL is nonzero then initialization happens in a vertically diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py new file mode 100644 index 000000000..9740b672a --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py @@ -0,0 +1,95 @@ +import plotly.graph_objects as go +import webviz_core_components as wcc +from dash import dcc, html +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import ViewElementABC + +from .._swatint import SwatinitQcDataModel +from ._fullscreen import FullScreen +from ._layout_style import LayoutStyle + + +class WaterViewelement(ViewElementABC): + """All elements visible in the 'Water Initialization QC plots'-tab + gathered in one viewelement""" + + class IDs: + # pylint: disable=too-few-public-methods + MAIN_FIGURE = "main-figure" + MAP_FIGURE = "map-figure" + + def __init__( + self, + datamodel: SwatinitQcDataModel, + main_figure: go.Figure, + map_figure: go.Figure, + qc_volumes: dict, + ) -> None: + super().__init__() + self.datamodel = datamodel + self.main_figure = main_figure + self.map_figure = map_figure + self.qc_volumes = qc_volumes + + def inner_layout(self) -> wcc.FlexBox: + return wcc.FlexBox( + children=[ + wcc.FlexColumn( + flex=4, + children=wcc.Graph( + style={"height": "85vh"}, + id=self.register_component_unique_id( + WaterViewelement.IDs.MAIN_FIGURE + ), + figure=self.main_figure, + ), + ), + wcc.FlexColumn( + flex=1, + children=[ + FullScreen( + wcc.Graph( + responsive=True, + style={"height": "100%", "min-height": "45vh"}, + id=self.register_component_unique_id( + WaterViewelement.IDs.MAP_FIGURE + ), + figure=self.map_figure, + ) + ), + self.info_box(self.qc_volumes, height="35vh"), + ], + ), + ] + ) + + @staticmethod + def info_box(qc_vols: dict, height: str) -> html.Div: + return html.Div( + [ + wcc.Header("Information about selection", style=LayoutStyle.HEADER), + html.Div( + "EQLNUMS:", style={"font-weight": "bold", "font-size": "15px"} + ), + html.Div(", ".join([str(x) for x in qc_vols["EQLNUMS"]])), + html.Div( + "SATNUMS:", style={"font-weight": "bold", "font-size": "15px"} + ), + html.Div(", ".join([str(x) for x in qc_vols["SATNUMS"]])), + html.Div( + html.Span("Reservoir Volume Difference:"), + style={"font-weight": "bold", "margin-top": "10px"}, + ), + html.Div( + children=[ + html.Div(line) + for line in [ + f"Water Volume Diff: {qc_vols['WVOL_DIFF']/(10**6):.2f} Mrm3", + f"Water Volume Diff (%): {qc_vols['WVOL_DIFF_PERCENT']:.2f}", + f"HC Volume Diff (%): {qc_vols['HCVOL_DIFF_PERCENT']:.2f}", + ] + ], + ), + ], + style={"height": height, "padding": "10px"}, + ) diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py index c98de5dac..38b5437f3 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_waterfall_plot.py @@ -1,10 +1,6 @@ from typing import List import plotly.graph_objects as go -from plotly.subplots import make_subplots - -from webviz_subsurface._figures import create_figure -from webviz_subsurface._utils.colors import hex_to_rgb, rgb_to_str, scale_rgb_lightness from .._plugin_ids import PlugInIDs diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py index 98c6f3689..c34d4c251 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py @@ -1 +1,3 @@ +from ._capilary_preassure import TabMaxPcInfoLayout from ._overview_info import OverviewTabLayout +from ._water_initialization import TabQqPlotLayout diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py index 1dbc118d9..b523053e9 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py @@ -1,143 +1,43 @@ -class TabMaxPcInfoLayout: - def __init__(self, get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: - self.datamodel = datamodel - self.get_uuid = get_uuid +import pandas as pd +import plotly.graph_objects as go +import webviz_core_components as wcc +from dash import Input, Output, State, callback, html +from dash.exceptions import PreventUpdate +from webviz_config.webviz_plugin_subclasses import ViewABC - def main_layout( - self, dframe: pd.DataFrame, selectors: list, map_figure: go.Figure - ) -> html.Div: - return html.Div( - children=[ - wcc.Header( - "Maximum capillary pressure scaling", style=LayoutStyle.HEADER - ), - wcc.FlexBox( - style={"margin-top": "10px", "height": "40vh"}, - children=[ - wcc.FlexColumn( - dcc.Markdown(pc_columns_description()), - flex=7, - style={"margin-right": "40px"}, - ), - wcc.FlexColumn( - FullScreen( - wcc.Graph( - style={"height": "100%", "min-height": "35vh"}, - figure=map_figure, - ) - ), - flex=3, - ), - ], - ), - self.create_max_pc_table(dframe, text_columns=selectors), - ] - ) +from .._plugin_ids import PlugInIDs +from .._swatint import SwatinitQcDataModel +from ..settings_groups import CapilarFilters, CapilarSelections +from ..views import CapilarViewelement - @property - def selections_layout(self) -> html.Div: - return html.Div( - children=[ - wcc.Selectors( - label="Selections", - children=[ - html.Div( - wcc.RadioItems( - label="Split table by:", - id=self.get_uuid(LayoutElements.GROUPBY_EQLNUM), - options=[ - {"label": "SATNUM", "value": "SATNUM"}, - {"label": "SATNUM and EQLNUM", "value": "both"}, - ], - value="SATNUM", - ), - style={"margin-bottom": "10px"}, - ), - self.scaling_threshold, - ], - ), - wcc.Selectors( - label="Filters", - children=[ - wcc.SelectWithLabel( - label="EQLNUM", - id=self.get_uuid(LayoutElements.TABLE_EQLNUM_SELECTOR), - options=[ - {"label": ens, "value": ens} - for ens in self.datamodel.eqlnums - ], - value=self.datamodel.eqlnums, - size=min(8, len(self.datamodel.eqlnums)), - multi=True, - ), - range_filters( - self.get_uuid(LayoutElements.FILTERS_CONTINOUS_MAX_PC), - self.datamodel, - ), - ], - ), - ] - ) - def create_max_pc_table( - self, dframe: pd.DataFrame, text_columns: list - ) -> dash_table: - return DashTable( - data=dframe.to_dict("records"), - columns=[ - { - "name": i, - "id": i, - "type": "numeric" if i not in text_columns else "text", - "format": {"specifier": ".4~r"} if i not in text_columns else {}, - } - for i in dframe.columns - ], - height="48vh", - sort_action="native", - fixed_rows={"headers": True}, - style_cell={ - "minWidth": LayoutStyle.TABLE_CELL_WIDTH, - "maxWidth": LayoutStyle.TABLE_CELL_WIDTH, - "width": LayoutStyle.TABLE_CELL_WIDTH, - }, - style_data_conditional=[ - { - "if": { - "filter_query": f"{{{self.datamodel.COLNAME_THRESHOLD}}} > 0", - }, - **LayoutStyle.TABLE_HIGHLIGHT, - }, - ], - ) +class TabMaxPcInfoLayout(ViewABC): + class IDs: + # pylint: disable=too-few-public-methods + CAPILAR_TAB = "capilar-tab" + MAIN_CLOUMN = "main-column" + + def __init__( + self, + datamodel: SwatinitQcDataModel, + dframe: pd.DataFrame, + selectors: list, + map_figure: go.Figure, + ) -> None: + super().__init__("Capillary pressure scaling") + self.datamodel = datamodel + self.dframe = dframe + self.selectors = selectors + self.map_figure = map_figure - @property - def scaling_threshold(self) -> html.Div: - return html.Div( - style={"margin-top": "10px"}, - children=[ - wcc.Label("Maximum PC_SCALING threshold"), - html.Div( - dcc.Input( - id=self.get_uuid(LayoutElements.HIGHLIGHT_ABOVE), - type="number", - persistence=True, - persistence_type="session", - ) - ), - ], + main_column = self.add_column(TabMaxPcInfoLayout.IDs.MAIN_CLOUMN) + row = main_column.make_row() + row.add_view_element( + CapilarViewelement(self.dframe, self.selectors, self.map_figure), + TabMaxPcInfoLayout.IDs.CAPILAR_TAB, ) + self.add_settings_group(CapilarSelections(self.datamodel)) + self.add_settings_group(CapilarFilters(self.datamodel)) -# pylint: disable=anomalous-backslash-in-string -def pc_columns_description() -> str: # tekst i figuren på siste side - return f""" -> **Column descriptions** -> - **PCOW_MAX** - Maximum capillary pressure from the input SWOF/SWFN tables -> - **PC_SCALING** - Maximum capillary pressure scaling applied -> - **PPCW** - Maximum capillary pressure after scaling -> - **{SwatinitQcDataModel.COLNAME_THRESHOLD}** - Column showing how many percent of the pc-scaled dataset that match the user-selected threshold -*PPCW = PCOW_MAX \* PC_SCALING* -A threshold for the maximum capillary scaling can be set in the menu. -The table will show how many percent of the dataset that exceeds this value, and cells above the threshold will be shown in the map ➡️ -""" + # set callbacks diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py index 003145058..7a83c8ec9 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py @@ -29,7 +29,7 @@ def set_callbacks(self) -> None: Output( self.view_element(OverviewTabLayout.IDs.OVERVIEW_TAB) .component_unique_id(OverviewViewelement.IDs.INFO_DIALOG) - .to_string(), # litt usikker på denne IDen her? + .to_string(), "open", ), Input(self.get_store_unique_id(PlugInIDs.Stores.Overview.BUTTON), "data"), diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py index f62905332..27a0102c2 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py @@ -1,197 +1,42 @@ -from typing import Dict, List, Optional, Tuple, Union - -from typing import Any, Callable, List, Optional - -import pandas as pd import plotly.graph_objects as go -import webviz_core_components as wcc -from dash import dash_table, dcc, html - -import pandas as pd -from dash import ( - ClientsideFunction, - Input, - Output, - State, - callback, - callback_context, - clientside_callback, -) -from dash.exceptions import PreventUpdate -from webviz_config import WebvizSettings -from webviz_config.webviz_assets import WEBVIZ_ASSETS +from dash import Input, Output, State, callback from webviz_config.webviz_plugin_subclasses import ViewABC -import webviz_subsurface -from webviz_subsurface._components.tornado._tornado_bar_chart import TornadoBarChart -from webviz_subsurface._components.tornado._tornado_data import TornadoData -from webviz_subsurface._components.tornado._tornado_table import TornadoTable - from .._plugin_ids import PlugInIDs +from .._swatint import SwatinitQcDataModel +from ..settings_groups import WaterFilters, WaterSelections +from ..view_elements import WaterViewelement class TabQqPlotLayout(ViewABC): class IDs: - WATERFALL = "waterfall" - PROP_VS_DEPTH = "prop-vs-depth" + # pylint: disable=too-few-public-methods + WATER_TAB = "water-tab" + MAIN_COLUMN = "main-column" - def __init__(self, datamodel: SwatinitQcDataModel) -> None: - super().__init__() - self.datamodel = datamodel - - def main_layout( + def __init__( self, + datamodel: SwatinitQcDataModel, main_figure: go.Figure, map_figure: go.Figure, qc_volumes: dict, - ) -> wcc.FlexBox: - return wcc.FlexBox( - children=[ - wcc.FlexColumn( - flex=4, - children=wcc.Graph( - style={"height": "85vh"}, - id=self.get_uuid(LayoutElements.MAIN_FIGURE), - figure=main_figure, - ), - ), - wcc.FlexColumn( - flex=1, - children=[ - FullScreen( - wcc.Graph( - responsive=True, - style={"height": "100%", "min-height": "45vh"}, - id=self.get_uuid(LayoutElements.MAP_FIGURE), - figure=map_figure, - ) - ), - self.info_box(qc_volumes, height="35vh"), - ], - ), - ] + ) -> None: + super().__init__("Water Initialization QC plots") + self.datamodel = datamodel + self.main_figure = main_figure + self.map_figure = map_figure + self.qc_volumes = qc_volumes + + main_column = self.add_column(TabQqPlotLayout.IDs.MAIN_COLUMN) + row = main_column.make_row() + row.add_view_element( + WaterViewelement( + self.datamodel, self.main_figure, self.map_figure, self.qc_volumes + ), + TabQqPlotLayout.IDs.WATER_TAB, ) - @property - def selections_layout(self) -> html.Div: - return html.Div( - children=[ - html.Div( - wcc.Dropdown( - label="Select QC-visualization:", - id=self.get_uuid(LayoutElements.PLOT_SELECTOR), - options=[ - { - "label": "Waterfall plot for water vol changes", - "value": self.MainPlots.WATERFALL, - }, - { - "label": "Reservoir properties vs Depth", - "value": self.MainPlots.PROP_VS_DEPTH, - }, - ], - value=self.MainPlots.PROP_VS_DEPTH, - clearable=False, - ), - style={"margin-bottom": "15px"}, - ), - wcc.Selectors( - label="Selections", - children=[ - wcc.SelectWithLabel( - label="EQLNUM", - id=self.get_uuid(LayoutElements.PLOT_EQLNUM_SELECTOR), - options=[ - {"label": ens, "value": ens} - for ens in self.datamodel.eqlnums - ], - value=self.datamodel.eqlnums[:1], - size=min(8, len(self.datamodel.eqlnums)), - multi=True, - ), - wcc.Dropdown( - label="Color by", - id=self.get_uuid(LayoutElements.COLOR_BY), - options=[ - {"label": ens, "value": ens} - for ens in self.datamodel.color_by_selectors - ], - value="QC_FLAG", - clearable=False, - ), - wcc.Label("Max number of points:"), - dcc.Input( - id=self.get_uuid(LayoutElements.MAX_POINTS), - type="number", - value=5000, - ), - ], - ), - wcc.Selectors( - label="Filters", - children=[ - wcc.SelectWithLabel( - label="QC_FLAG", - id={ - "id": self.get_uuid(LayoutElements.FILTERS_DISCRETE), - "col": "QC_FLAG", - }, - options=[ - {"label": ens, "value": ens} - for ens in self.datamodel.qc_flag - ], - value=self.datamodel.qc_flag, - size=min(8, len(self.datamodel.qc_flag)), - ), - wcc.SelectWithLabel( - label="SATNUM", - id={ - "id": self.get_uuid(LayoutElements.FILTERS_DISCRETE), - "col": "SATNUM", - }, - options=[ - {"label": ens, "value": ens} - for ens in self.datamodel.satnums - ], - value=self.datamodel.satnums, - size=min(8, len(self.datamodel.satnums)), - ), - range_filters( - self.get_uuid(LayoutElements.FILTERS_CONTINOUS), - self.datamodel, - ), - ], - ), - ], - ) + self.add_settings_group(WaterSelections(self.datamodel)) + self.add_settings_group(WaterFilters(self.datamodel)) - @staticmethod - def info_box(qc_vols: dict, height: str) -> html.Div: - return html.Div( - [ - wcc.Header("Information about selection", style=LayoutStyle.HEADER), - html.Div( - "EQLNUMS:", style={"font-weight": "bold", "font-size": "15px"} - ), - html.Div(", ".join([str(x) for x in qc_vols["EQLNUMS"]])), - html.Div( - "SATNUMS:", style={"font-weight": "bold", "font-size": "15px"} - ), - html.Div(", ".join([str(x) for x in qc_vols["SATNUMS"]])), - html.Div( - html.Span("Reservoir Volume Difference:"), - style={"font-weight": "bold", "margin-top": "10px"}, - ), - html.Div( - children=[ - html.Div(line) - for line in [ - f"Water Volume Diff: {qc_vols['WVOL_DIFF']/(10**6):.2f} Mrm3", - f"Water Volume Diff (%): {qc_vols['WVOL_DIFF_PERCENT']:.2f}", - f"HC Volume Diff (%): {qc_vols['HCVOL_DIFF_PERCENT']:.2f}", - ] - ], - ), - ], - style={"height": height, "padding": "10px"}, - ) + # set callbacks From 8b8a884afa0cd1197ec6a448fe4486ce49b1af59 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Wed, 3 Aug 2022 15:56:55 +0200 Subject: [PATCH 5/7] saving --- .../plugins/_swatinit_qc/_plugin.py | 101 +++++++++++- .../plugins/_swatinit_qc/_plugin_ids.py | 8 + .../_swatinit_qc/settings_groups/__init__.py | 2 +- .../settings_groups/_caplilar_settings.py | 8 +- .../settings_groups/_water_settings.py | 9 +- .../_swatinit_qc/shared_settings/__init__.py | 1 - .../_swatinit_qc/shared_settings/_pick_tab.py | 44 ------ .../_swatinit_qc/view_elements/__init__.py | 1 + .../view_elements/_capilar_layout.py | 55 +++++-- .../view_elements/_water_layout.py | 1 + .../_swatinit_qc/views/_capilary_preassure.py | 144 ++++++++++++++++-- .../views/_water_initialization.py | 95 +++++++++++- 12 files changed, 375 insertions(+), 94 deletions(-) delete mode 100644 webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py delete mode 100644 webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py index e707e0f60..440053e82 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Optional +from typing import List, Optional, Tuple import webviz_core_components as wcc from webviz_config import WebvizPluginABC, WebvizSettings @@ -7,7 +7,6 @@ from ._error import error from ._plugin_ids import PlugInIDs from ._swatint import SwatinitQcDataModel -from .shared_settings import PickTab from .views import OverviewTabLayout, TabMaxPcInfoLayout, TabQqPlotLayout @@ -31,11 +30,6 @@ def __init__( ) self.error_message = "" - self.add_store( - PlugInIDs.Stores.Shared.PICK_VIEW, WebvizPluginABC.StorageType.SESSION - ) - self.add_shared_settings_group(PickTab(), PlugInIDs.SharedSettings.PICK_VIEW) - # Stores used in Overview tab self.add_store( PlugInIDs.Stores.Overview.BUTTON, WebvizPluginABC.StorageType.SESSION @@ -69,13 +63,106 @@ def __init__( self.add_store( PlugInIDs.Stores.Capilary.MAX_PC_SCALE, WebvizPluginABC.StorageType.SESSION ) + self.add_store( + PlugInIDs.Stores.Capilary.EQLNUM, WebvizPluginABC.StorageType.SESSION + ) self.add_view( OverviewTabLayout(self._datamodel), PlugInIDs.SwatinitViews.OVERVIEW, PlugInIDs.SwatinitViews.GROUP_NAME, ) + """self.add_view( + TabQqPlotLayout(self._datamodel), + PlugInIDs.SwatinitViews.WATER, + PlugInIDs.SwatinitViews.GROUP_NAME + )""" + self.add_view( + TabMaxPcInfoLayout(self._datamodel), + PlugInIDs.SwatinitViews.WATER, + PlugInIDs.SwatinitViews.GROUP_NAME + ) @property def layout(self) -> wcc.Tabs: return error(self.error_message) +""" + def add_webvizstore(self) -> List[Tuple[Callable, List[dict]]]: + return self._datamodel.webviz_store + + + + +def plugin_callbacks(get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: + qc_plot_layout = TabQqPlotLayout(get_uuid, datamodel) + table_layout = TabMaxPcInfoLayout(get_uuid, datamodel) + + @callback( + Output(get_uuid(LayoutElements.PLOT_WRAPPER), "children"), + Input(get_uuid(LayoutElements.SELECTED_TAB), "value"), + Input(get_uuid(LayoutElements.PLOT_EQLNUM_SELECTOR), "value"), + Input({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "value"), + Input({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "value"), + Input(get_uuid(LayoutElements.COLOR_BY), "value"), + Input(get_uuid(LayoutElements.MAX_POINTS), "value"), + Input(get_uuid(LayoutElements.PLOT_SELECTOR), "value"), + State({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "id"), + State({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "id"), + ) + # pylint: disable=too-many-arguments + def _update_plot( + tab_selected: str, + eqlnums: List[str], + dicrete_filters: List[List[str]], + continous_filters: List[List[str]], + color_by: str, + max_points: int, + plot_selector: str, + dicrete_filters_ids: List[Dict[str, str]], + continous_filters_ids: List[Dict[str, str]], + ) -> list: + + if tab_selected != Tabs.QC_PLOTS or max_points is None: + raise PreventUpdate + + filters = zip_filters(dicrete_filters, dicrete_filters_ids) + filters.update({"EQLNUM": eqlnums}) + + df = datamodel.get_dataframe( + filters=filters, + range_filters=zip_filters(continous_filters, continous_filters_ids), + ) + if df.empty: + return ["No data left after filtering"] + + qc_volumes = datamodel.compute_qc_volumes(df) + + df = datamodel.filter_dframe_on_depth(df) + df = datamodel.resample_dataframe(df, max_points=max_points) + + colormap = datamodel.create_colormap(color_by) + main_plot = ( + WaterfallPlot(qc_vols=qc_volumes).figure + if plot_selector == qc_plot_layout.MainPlots.WATERFALL + else PropertiesVsDepthSubplots( + dframe=df, + color_by=color_by, + colormap=colormap, + discrete_color=color_by in datamodel.SELECTORS, + ).figure + ) + map_figure = MapFigure( + dframe=df, + color_by=color_by, + faultlinedf=datamodel.faultlines_df, + colormap=colormap, + ).figure + + return qc_plot_layout.main_layout( + main_figure=main_plot, + map_figure=map_figure, + qc_volumes=qc_volumes, + ) + + +""" \ No newline at end of file diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py index e702280a5..fdeebfb87 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py @@ -29,12 +29,20 @@ class Capilary: # pylint: disable=too-few-public-methods SPLIT_TABLE_BY = "split-table-by" MAX_PC_SCALE = "max-pc-scale" + EQLNUM = "eqlnum" class SharedSettings: # pylint: disable=too-few-public-methods PICK_VIEW = "pick-view" FILTERS = "filters" + class SettingsGroups: + # pylint: disable=too-few-public-methods + WATER_SEELECTORS = "water-selectors" + WATER_FILTERS = "water-filters" + CAPILAR_SELECTORS = "capilar-selectors" + CAPILAR_FILTERS = "capilar-filters" + class QcFlags: # pylint: disable=too-few-public-methods FINE_EQUIL = "FINE_EQUIL" diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py index 3db3ef449..7c112e759 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py @@ -1,2 +1,2 @@ -from ._capilar_settings import CapilarFilters, CapilarSelections +from ._caplilar_settings import CapilarFilters, CapilarSelections from ._water_settings import WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py index a38c85cec..7a1f8a29f 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py @@ -92,13 +92,11 @@ def layout(self) -> List[Component]: size=min(8, len(self.datamodel.eqlnums)), multi=True, ), - self.range_filters( - CapilarFilters.IDs.RANGE_FILTERS, - ), + self.range_filters, ] @property - def range_filters(self, uuid: str) -> List: + def range_filters(self) -> List: dframe = self.datamodel.dframe filters = [] for col in self.datamodel.filters_continuous: @@ -106,7 +104,7 @@ def range_filters(self, uuid: str) -> List: filters.append( wcc.RangeSlider( label="Depth range" if col == "Z" else col, - id={"id": uuid, "col": col}, + id={"id": CapilarFilters.IDs.RANGE_FILTERS, "col": col}, min=min_val, max=max_val, value=[min_val, max_val], diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py index ee631bbea..73fc3f71a 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py @@ -130,14 +130,11 @@ def layout(self) -> List[Component]: value=self.datamodel.satnums, size=min(8, len(self.datamodel.satnums)), ), - self.range_filters( - WaterFilters.IDs.RANGE_FILTERS, - self.datamodel, - ), + self.range_filters, ] @property - def range_filters(self, uuid: str) -> List: + def range_filters(self) -> List: dframe = self.datamodel.dframe filters = [] for col in self.datamodel.filters_continuous: @@ -145,7 +142,7 @@ def range_filters(self, uuid: str) -> List: filters.append( wcc.RangeSlider( label="Depth range" if col == "Z" else col, - id={"id": uuid, "col": col}, + id={"id": WaterFilters.IDs.RANGE_FILTERS, "col": col}, min=min_val, max=max_val, value=[min_val, max_val], diff --git a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py deleted file mode 100644 index e113e43c6..000000000 --- a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._pick_tab import PickTab diff --git a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py b/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py deleted file mode 100644 index c967eb3d1..000000000 --- a/webviz_subsurface/plugins/_swatinit_qc/shared_settings/_pick_tab.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import List - -import webviz_core_components as wcc -from dash import Input, Output, callback, html -from dash.development.base_component import Component -from webviz_config.webviz_plugin_subclasses import SettingsGroupABC - -from .._plugin_ids import PlugInIDs - - -class PickTab(SettingsGroupABC): - class IDs: - # pylint: diable=too-few-public-methods - TAB_PICKER = "tab-picker" - - def __init__(self) -> None: - super().__init__("Pick Tab") - self.tab_options = [ - {"label": "Overview and Information", "value": "info"}, - {"label": "Water Initalization QC plots", "value": "water"}, - {"label": "Capillart preassure scaling", "value": "capilar"}, - ] - - def layout(self) -> Component: - return wcc.RadioItems( - id=self.register_component_unique_id(PickTab.IDs.TAB_PICKER), - options=self.tab_options, - value="info", - inline=False, - ) - - def set_callbacks(self) -> None: - @callback( - Output( - self.get_store_unique_id(PlugInIDs.Stores.Shared.PICK_VIEW), - "data", - ), - Input( - self.component_unique_id(PickTab.IDs.TAB_PICKER).to_string(), - "value", - ), - ) - def _set_tab(tab: str) -> str: - return tab diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py index c254604af..d7e57ed74 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py @@ -2,5 +2,6 @@ from ._dash_table import DashTable from ._fullscreen import FullScreen from ._layout_style import LayoutStyle +from ._map_figure import MapFigure from ._overview_layout import OverviewViewelement from ._water_layout import WaterViewelement diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py index 2ac9c324a..a9c44a450 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py @@ -1,3 +1,4 @@ +from cgi import print_arguments from typing import List import pandas as pd @@ -8,9 +9,11 @@ from webviz_config.webviz_plugin_subclasses import ViewElementABC from .._swatint import SwatinitQcDataModel +from ..settings_groups import CapilarFilters from ._dash_table import DashTable from ._fullscreen import FullScreen from ._layout_style import LayoutStyle +from ._map_figure import MapFigure class CapilarViewelement(ViewElementABC): @@ -26,15 +29,38 @@ class IDs: def __init__( self, datamodel: SwatinitQcDataModel, - dframe: pd.DataFrame, - selectors: list, - map_figure: go.Figure, ) -> None: super().__init__() self.datamodel = datamodel - self.dframe = dframe - self.selectors = selectors - self.map_figure = map_figure + + self.init_eqlnums = self.datamodel.eqlnums[:1] + continous_filters = ([ + self.datamodel.dframe[col].min(), + self.datamodel.dframe[col].max() + ] for col in self.datamodel.filters_continuous) + + continous_filters_ids = ([ + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": col, + } + ] for col in self.datamodel.filters_continuous) + + print({"EQLNUM": self.init_eqlnums}) + self.dframe = self.datamodel.get_dataframe( + filters={"EQLNUM": self.init_eqlnums}, + range_filters=zip_filters(continous_filters, continous_filters_ids), + ) + + df_for_map = datamodel.resample_dataframe(self.dframe, max_points=10000) + self.selectors = self.datamodel.SELECTORS + + self.map_figure = MapFigure( + dframe=df_for_map, + color_by="EQLNUM", + faultlinedf=datamodel.faultlines_df, + colormap=datamodel.create_colormap("EQLNUM"), + ).figure def inner_layout(self) -> List[Component]: return [ @@ -64,19 +90,20 @@ def inner_layout(self) -> List[Component]: ), ], ), - self.max_pc_table(text_columns=self.selectors), + self.max_pc_table, ] @property - def max_pc_table(self, text_columns: list) -> dash_table: + def max_pc_table(self) -> dash_table: return DashTable( + id=self.register_component_unique_id(CapilarViewelement.IDs.TABLE), data=self.dframe.to_dict("records"), columns=[ { "name": i, "id": i, - "type": "numeric" if i not in text_columns else "text", - "format": {"specifier": ".4~r"} if i not in text_columns else {}, + "type": "numeric" if i not in self.selectors else "text", + "format": {"specifier": ".4~r"} if i not in self.selectors else {}, } for i in self.dframe.columns ], @@ -99,6 +126,7 @@ def max_pc_table(self, text_columns: list) -> dash_table: ) +# pylint: disable=anomalous-backslash-in-string def pc_columns_description() -> str: return f""" > **Column descriptions** @@ -110,3 +138,10 @@ def pc_columns_description() -> str: A threshold for the maximum capillary scaling can be set in the menu. The table will show how many percent of the dataset that exceeds this value, and cells above the threshold will be shown in the map ➡️ """ + + +def zip_filters(filter_values: list, filter_ids: list) -> dict: + for values, id_val in zip(filter_values, filter_ids): + print("val: ", values) + print("id: ", id_val) + return {id_val["col"]: values for values, id_val in zip(filter_values, filter_ids)} diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py index 9740b672a..f926d5154 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py @@ -27,6 +27,7 @@ def __init__( ) -> None: super().__init__() self.datamodel = datamodel + self.main_figure = main_figure self.map_figure = map_figure self.qc_volumes = qc_volumes diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py index b523053e9..8d371fbd4 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py @@ -1,14 +1,12 @@ -import pandas as pd -import plotly.graph_objects as go -import webviz_core_components as wcc -from dash import Input, Output, State, callback, html -from dash.exceptions import PreventUpdate +from typing import Dict, List, Optional + +from dash import ALL, Input, Output, State, callback, html from webviz_config.webviz_plugin_subclasses import ViewABC from .._plugin_ids import PlugInIDs from .._swatint import SwatinitQcDataModel from ..settings_groups import CapilarFilters, CapilarSelections -from ..views import CapilarViewelement +from ..view_elements import CapilarViewelement, MapFigure class TabMaxPcInfoLayout(ViewABC): @@ -20,24 +18,138 @@ class IDs: def __init__( self, datamodel: SwatinitQcDataModel, - dframe: pd.DataFrame, - selectors: list, - map_figure: go.Figure, ) -> None: super().__init__("Capillary pressure scaling") self.datamodel = datamodel - self.dframe = dframe - self.selectors = selectors - self.map_figure = map_figure + self.selectors = self.datamodel.SELECTORS main_column = self.add_column(TabMaxPcInfoLayout.IDs.MAIN_CLOUMN) row = main_column.make_row() row.add_view_element( - CapilarViewelement(self.dframe, self.selectors, self.map_figure), + CapilarViewelement(self.datamodel), TabMaxPcInfoLayout.IDs.CAPILAR_TAB, ) - self.add_settings_group(CapilarSelections(self.datamodel)) - self.add_settings_group(CapilarFilters(self.datamodel)) + self.add_settings_group(CapilarSelections(self.datamodel), PlugInIDs.SettingsGroups.CAPILAR_SELECTORS) + self.add_settings_group(CapilarFilters(self.datamodel), PlugInIDs.SettingsGroups.CAPILAR_FILTERS) + + def set_callbacks(self) -> None: + # update map + @callback( + Output( + self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) + .component_unique_id(CapilarViewelement.IDs.MAP) + .to_string(), + "figure", + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.MAX_PC_SCALE), + "data", + ), + Input( + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": ALL + }, + "value", + ), + State( + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": ALL + }, + "id", + ), + ) + def _update_map( + eqlnums: list, + threshold: Optional[int], + continous_filters: List[List[str]], + continous_filters_ids: List[Dict[str, str]] + ) -> MapFigure: + df = self.datamodel.get_dataframe( + filters={"EQLNUM": eqlnums}, + range_filters=zip_filters(continous_filters, continous_filters_ids), + ) + df_for_map = df[df["PC_SCALING"] >= threshold] + if threshold is None: + df_for_map = self.datamodel.resample_dataframe(df, max_points=10000) + + return MapFigure( + dframe=df_for_map, + color_by="EQLNUM", + faultlinedf=self.datamodel.faultlines_df, + colormap=self.datamodel.create_colormap("EQLNUM"), + ).figure + + # update table + @callback( + Output( + self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) + .component_unique_id(CapilarViewelement.IDs.TABLE) + .to_string(), + "columns" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.MAX_PC_SCALE), + "data", + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.SPLIT_TABLE_BY), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), + "data" + ), + Input( + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": ALL + }, + "value", + ), + State( + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": ALL + }, + "id", + ), + ) + def _update_table( + threshold: Optional[list], + groupby_eqlnum: list, + eqlnums: list, + continous_filters: List[List[str]], + continous_filters_ids: List[Dict[str, str]] + ) -> List[dict]: + df = self.datamodel.get_dataframe( + filters={"EQLNUM": eqlnums}, + range_filters=zip_filters(continous_filters, continous_filters_ids), + ) + dframe = self.datamodel.get_max_pc_info_and_percent_for_data_matching_condition( + dframe=df, + condition=threshold, + groupby_eqlnum=groupby_eqlnum == "both", + ) + text_columns=self.selectors + columns=[ + { + "name": i, + "id": i, + "type": "numeric" if i not in text_columns else "text", + "format": {"specifier": ".4~r"} if i not in text_columns else {}, + } + for i in dframe.columns + ] + return columns + - # set callbacks +def zip_filters(filter_values: list, filter_ids: list) -> dict: + print(zip(filter_values, filter_ids)) + return {id_val["col"]: values for values, id_val in zip(filter_values, filter_ids)} diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py index 27a0102c2..1a685fb7d 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py @@ -1,5 +1,7 @@ +from typing import Optional, Tuple, Union + import plotly.graph_objects as go -from dash import Input, Output, State, callback +from dash import Input, Output, State, callback, callback_context from webviz_config.webviz_plugin_subclasses import ViewABC from .._plugin_ids import PlugInIDs @@ -36,7 +38,92 @@ def __init__( TabQqPlotLayout.IDs.WATER_TAB, ) - self.add_settings_group(WaterSelections(self.datamodel)) - self.add_settings_group(WaterFilters(self.datamodel)) + self.add_settings_group(WaterSelections(self.datamodel), PlugInIDs.SettingsGroups.WATER_SEELECTORS) + self.add_settings_group(WaterFilters(self.datamodel), PlugInIDs.SettingsGroups.WATER_FILTERS) + + + def set_callbacks(self) -> None: + # update + + + @callback( + Output( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) + .to_string(), + "figure" + ), + Output( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) + .to_string(), + "figure" + ), + Input( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) + .to_string(), + "selectedData" + ), + Input( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) + .to_string(), + "selectedData" + ), + State( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) + .to_string(), + "figure" + ), + State( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) + .to_string(), + "figure" + ), + ) + def _update_selected_points_in_figure(selected_main: dict, selected_map: dict, mainfig: dict, mapfig: dict) -> Tuple[dict, dict]: + ctx = callback_context.triggered[0]["prop_id"] + + selected = ( + selected_map if WaterViewelement.IDs.MAP_FIGURE in ctx else selected_main + ) + point_indexes = get_point_indexes_from_selected(selected) + + for trace in mainfig["data"]: + update_selected_points_in_trace(trace, point_indexes) + for trace in mapfig["data"]: + update_selected_points_in_trace(trace, point_indexes) + + return mainfig, mapfig + + + def get_point_indexes_from_selected(selected: Optional[dict]) -> Union[list, dict]: + if not (isinstance(selected, dict) and "points" in selected): + return [] + + continous_color = "marker.color" in selected["points"][0] + if continous_color: + return [point["pointNumber"] for point in selected["points"]] + + point_indexes: dict = {} + for point in selected["points"]: + trace_name = str(point["customdata"][0]) + if trace_name not in point_indexes: + point_indexes[trace_name] = [] + point_indexes[trace_name].append(point["pointNumber"]) + return point_indexes + - # set callbacks + def update_selected_points_in_trace( + trace: dict, point_indexes: Union[dict, list] + ) -> None: + if "name" in trace: + selectedpoints = ( + point_indexes + if isinstance(point_indexes, list) + else point_indexes.get(trace["name"], []) + ) + trace.update(selectedpoints=selectedpoints if point_indexes else None) \ No newline at end of file From cf6b97a71b3ebfd88af125d719b765e7c44a4242 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Thu, 4 Aug 2022 12:54:52 +0200 Subject: [PATCH 6/7] saving --- .../plugins/_swatinit_qc/_plugin.py | 86 +----------- .../settings_groups/_caplilar_settings.py | 4 +- .../settings_groups/_water_settings.py | 3 +- .../view_elements/_water_layout.py | 31 +++-- .../_swatinit_qc/views/_capilary_preassure.py | 8 +- .../views/_water_initialization.py | 127 +++++++++++++++++- 6 files changed, 160 insertions(+), 99 deletions(-) diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py index 440053e82..033b89b95 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List, Optional, Tuple +from typing import Callable, List, Optional, Tuple import webviz_core_components as wcc from webviz_config import WebvizPluginABC, WebvizSettings @@ -19,7 +19,7 @@ def __init__( realization: Optional[int] = None, faultlines: Path = None, ) -> None: - super().__init__() + super().__init__(stretch=True) self._datamodel = SwatinitQcDataModel( webviz_settings=webviz_settings, @@ -72,11 +72,11 @@ def __init__( PlugInIDs.SwatinitViews.OVERVIEW, PlugInIDs.SwatinitViews.GROUP_NAME, ) - """self.add_view( + self.add_view( TabQqPlotLayout(self._datamodel), PlugInIDs.SwatinitViews.WATER, PlugInIDs.SwatinitViews.GROUP_NAME - )""" + ) self.add_view( TabMaxPcInfoLayout(self._datamodel), PlugInIDs.SwatinitViews.WATER, @@ -86,83 +86,7 @@ def __init__( @property def layout(self) -> wcc.Tabs: return error(self.error_message) -""" + def add_webvizstore(self) -> List[Tuple[Callable, List[dict]]]: return self._datamodel.webviz_store - - - -def plugin_callbacks(get_uuid: Callable, datamodel: SwatinitQcDataModel) -> None: - qc_plot_layout = TabQqPlotLayout(get_uuid, datamodel) - table_layout = TabMaxPcInfoLayout(get_uuid, datamodel) - - @callback( - Output(get_uuid(LayoutElements.PLOT_WRAPPER), "children"), - Input(get_uuid(LayoutElements.SELECTED_TAB), "value"), - Input(get_uuid(LayoutElements.PLOT_EQLNUM_SELECTOR), "value"), - Input({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "value"), - Input({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "value"), - Input(get_uuid(LayoutElements.COLOR_BY), "value"), - Input(get_uuid(LayoutElements.MAX_POINTS), "value"), - Input(get_uuid(LayoutElements.PLOT_SELECTOR), "value"), - State({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "id"), - State({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "id"), - ) - # pylint: disable=too-many-arguments - def _update_plot( - tab_selected: str, - eqlnums: List[str], - dicrete_filters: List[List[str]], - continous_filters: List[List[str]], - color_by: str, - max_points: int, - plot_selector: str, - dicrete_filters_ids: List[Dict[str, str]], - continous_filters_ids: List[Dict[str, str]], - ) -> list: - - if tab_selected != Tabs.QC_PLOTS or max_points is None: - raise PreventUpdate - - filters = zip_filters(dicrete_filters, dicrete_filters_ids) - filters.update({"EQLNUM": eqlnums}) - - df = datamodel.get_dataframe( - filters=filters, - range_filters=zip_filters(continous_filters, continous_filters_ids), - ) - if df.empty: - return ["No data left after filtering"] - - qc_volumes = datamodel.compute_qc_volumes(df) - - df = datamodel.filter_dframe_on_depth(df) - df = datamodel.resample_dataframe(df, max_points=max_points) - - colormap = datamodel.create_colormap(color_by) - main_plot = ( - WaterfallPlot(qc_vols=qc_volumes).figure - if plot_selector == qc_plot_layout.MainPlots.WATERFALL - else PropertiesVsDepthSubplots( - dframe=df, - color_by=color_by, - colormap=colormap, - discrete_color=color_by in datamodel.SELECTORS, - ).figure - ) - map_figure = MapFigure( - dframe=df, - color_by=color_by, - faultlinedf=datamodel.faultlines_df, - colormap=colormap, - ).figure - - return qc_plot_layout.main_layout( - main_figure=main_plot, - map_figure=map_figure, - qc_volumes=qc_volumes, - ) - - -""" \ No newline at end of file diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py index 7a1f8a29f..480a1c805 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py @@ -1,3 +1,4 @@ +from logging import captureWarnings from typing import List import webviz_core_components as wcc @@ -79,6 +80,7 @@ class IDs: def __init__(self, datamodel: SwatinitQcDataModel) -> None: super().__init__("Filter") self.datamodel = datamodel + self.range_filters_id = self.register_component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) def layout(self) -> List[Component]: return [ @@ -104,7 +106,7 @@ def range_filters(self) -> List: filters.append( wcc.RangeSlider( label="Depth range" if col == "Z" else col, - id={"id": CapilarFilters.IDs.RANGE_FILTERS, "col": col}, + id={"id": self.range_filters_id, "col": col}, min=min_val, max=max_val, value=[min_val, max_val], diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py index 73fc3f71a..14235ed9a 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py @@ -109,6 +109,7 @@ class IDs: def __init__(self, datamodel) -> None: super().__init__("Filters") self.datamodel = datamodel + self.range_filters_id = self.register_component_unique_id(WaterFilters.IDs.RANGE_FILTERS) def layout(self) -> List[Component]: return [ @@ -142,7 +143,7 @@ def range_filters(self) -> List: filters.append( wcc.RangeSlider( label="Depth range" if col == "Z" else col, - id={"id": WaterFilters.IDs.RANGE_FILTERS, "col": col}, + id={"id": self.range_filters_id, "col": col}, min=min_val, max=max_val, value=[min_val, max_val], diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py index f926d5154..63ad7c16c 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py @@ -17,6 +17,9 @@ class IDs: # pylint: disable=too-few-public-methods MAIN_FIGURE = "main-figure" MAP_FIGURE = "map-figure" + INFO_BOX_EQLNUMS = "info-box-eqlnums" + INFO_BOX_SATNUMS = "infobox-satnums" + INFO_BOX_VOL_DIFF = "info-box-vol-diff" def __init__( self, @@ -58,29 +61,38 @@ def inner_layout(self) -> wcc.FlexBox: figure=self.map_figure, ) ), - self.info_box(self.qc_volumes, height="35vh"), + self.info_box, ], ), ] ) - @staticmethod - def info_box(qc_vols: dict, height: str) -> html.Div: + @property + def info_box(self) -> html.Div: + qc_vols = self.qc_volumes + height = "35vh" return html.Div( [ wcc.Header("Information about selection", style=LayoutStyle.HEADER), html.Div( - "EQLNUMS:", style={"font-weight": "bold", "font-size": "15px"} + "EQLNUMS:", + style={"font-weight": "bold", "font-size": "15px"}, ), - html.Div(", ".join([str(x) for x in qc_vols["EQLNUMS"]])), html.Div( - "SATNUMS:", style={"font-weight": "bold", "font-size": "15px"} + ", ".join([str(x) for x in qc_vols["EQLNUMS"]]), + id=self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_EQLNUMS), + ), + html.Div( + "SATNUMS:", + style={"font-weight": "bold", "font-size": "15px"}, ), - html.Div(", ".join([str(x) for x in qc_vols["SATNUMS"]])), html.Div( - html.Span("Reservoir Volume Difference:"), - style={"font-weight": "bold", "margin-top": "10px"}, + ", ".join([str(x) for x in qc_vols["SATNUMS"]]), + id= self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_SATNUMS), ), + html.Div( + html.Span("Reservoir Volume Difference:"), + style={"font-weight": "bold", "margin-top": "10px"}, ), html.Div( children=[ html.Div(line) @@ -90,6 +102,7 @@ def info_box(qc_vols: dict, height: str) -> html.Div: f"HC Volume Diff (%): {qc_vols['HCVOL_DIFF_PERCENT']:.2f}", ] ], + id=self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_VOL_DIFF) ), ], style={"height": height, "padding": "10px"}, diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py index 8d371fbd4..2ec2ac891 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py @@ -52,14 +52,18 @@ def set_callbacks(self) -> None: ), Input( { - "id": CapilarFilters.IDs.RANGE_FILTERS, + "id": self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) + .component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) + .to_string(), # litt usikker på denne...? "col": ALL }, "value", ), State( { - "id": CapilarFilters.IDs.RANGE_FILTERS, + "id": self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) + .component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) + .to_string(), "col": ALL }, "id", diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py index 1a685fb7d..7042465ee 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py @@ -8,6 +8,7 @@ from .._swatint import SwatinitQcDataModel from ..settings_groups import WaterFilters, WaterSelections from ..view_elements import WaterViewelement +from webviz_subsurface.plugins._swatinit_qc import view_elements class TabQqPlotLayout(ViewABC): @@ -19,12 +20,12 @@ class IDs: def __init__( self, datamodel: SwatinitQcDataModel, - main_figure: go.Figure, - map_figure: go.Figure, - qc_volumes: dict, ) -> None: super().__init__("Water Initialization QC plots") self.datamodel = datamodel + + + self.main_figure = main_figure self.map_figure = map_figure self.qc_volumes = qc_volumes @@ -44,8 +45,124 @@ def __init__( def set_callbacks(self) -> None: # update - - + @callback( + Output( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) + .to_string(), + "figure" + ), + Output( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) + .to_string(), + "figure" + ), + Output( + self.view_element(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.INFO_BOX_EQLNUMS) + .to_string(), + "children" + ), + Output( + self.view_elements(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.INFO_BOX_SATNUMS), + "children" + ), + Output( + self.view_elements(TabQqPlotLayout.IDs.WATER_TAB) + .component_unique_id(WaterViewelement.IDs.INFO_BOX_VOL_DIFF), + "children" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.QC_VIZ), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.COLOR_BY), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.MAX_POINTS), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.QC_FLAG), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.SATNUM), + "data" + ), + Input( + self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), + "data" + ), + + Input({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "value"), + Input({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "value"), + State({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "id"), + State({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "id"), + ) + # pylint: disable=too-many-arguments + def _update_plot( + tab_selected: str, + eqlnums: List[str], + dicrete_filters: List[List[str]], + continous_filters: List[List[str]], + color_by: str, + max_points: int, + plot_selector: str, + dicrete_filters_ids: List[Dict[str, str]], + continous_filters_ids: List[Dict[str, str]], + ) -> list: + + if tab_selected != Tabs.QC_PLOTS or max_points is None: + raise PreventUpdate + + filters = zip_filters(dicrete_filters, dicrete_filters_ids) + filters.update({"EQLNUM": eqlnums}) + + df = datamodel.get_dataframe( + filters=filters, + range_filters=zip_filters(continous_filters, continous_filters_ids), + ) + if df.empty: + return ["No data left after filtering"] + + qc_volumes = datamodel.compute_qc_volumes(df) + + df = datamodel.filter_dframe_on_depth(df) + df = datamodel.resample_dataframe(df, max_points=max_points) + + colormap = datamodel.create_colormap(color_by) + main_plot = ( + WaterfallPlot(qc_vols=qc_volumes).figure + if plot_selector == qc_plot_layout.MainPlots.WATERFALL + else PropertiesVsDepthSubplots( + dframe=df, + color_by=color_by, + colormap=colormap, + discrete_color=color_by in datamodel.SELECTORS, + ).figure + ) + map_figure = MapFigure( + dframe=df, + color_by=color_by, + faultlinedf=datamodel.faultlines_df, + colormap=colormap, + ).figure + + return qc_plot_layout.main_layout( + main_figure=main_plot, + map_figure=map_figure, + qc_volumes=qc_volumes, + ) + @callback( Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) From f4ed7781a8b365bfc05c5f1a66cb83432c3d9c84 Mon Sep 17 00:00:00 2001 From: "Viktoria Christine Vahlin (OG SUB RPE)" Date: Fri, 5 Aug 2022 12:52:29 +0200 Subject: [PATCH 7/7] resorting files after views --- .../plugins/_swatinit_qc/_plugin.py | 6 +- .../plugins/_swatinit_qc/_plugin_ids.py | 12 +- .../_swatinit_qc/view_elements/__init__.py | 2 + .../view_elements/_capilar_layout.py | 29 ++-- .../view_elements/_overview_layout.py | 2 + .../view_elements/_water_layout.py | 21 ++- .../plugins/_swatinit_qc/views/__init__.py | 6 +- .../views/capilar_tab/__init__.py | 2 + .../{ => capilar_tab}/_capilary_preassure.py | 78 ++++----- .../capilar_tab/settings}/__init__.py | 1 - .../settings}/_caplilar_settings.py | 8 +- .../views/overbiew_tab/__init__.py | 1 + .../{ => overbiew_tab}/_overview_info.py | 6 +- .../_swatinit_qc/views/water_tab/__init__.py | 2 + .../{ => water_tab}/_water_initialization.py | 163 ++++++++---------- .../views/water_tab/settings/__init__.py | 1 + .../water_tab/settings}/_water_settings.py | 48 ++---- 17 files changed, 187 insertions(+), 201 deletions(-) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/__init__.py rename webviz_subsurface/plugins/_swatinit_qc/views/{ => capilar_tab}/_capilary_preassure.py (68%) rename webviz_subsurface/plugins/_swatinit_qc/{settings_groups => views/capilar_tab/settings}/__init__.py (52%) rename webviz_subsurface/plugins/_swatinit_qc/{settings_groups => views/capilar_tab/settings}/_caplilar_settings.py (96%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/__init__.py rename webviz_subsurface/plugins/_swatinit_qc/views/{ => overbiew_tab}/_overview_info.py (92%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/water_tab/__init__.py rename webviz_subsurface/plugins/_swatinit_qc/views/{ => water_tab}/_water_initialization.py (60%) create mode 100644 webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/__init__.py rename webviz_subsurface/plugins/_swatinit_qc/{settings_groups => views/water_tab/settings}/_water_settings.py (82%) diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py index 033b89b95..9a5b45c2f 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin.py @@ -67,6 +67,7 @@ def __init__( PlugInIDs.Stores.Capilary.EQLNUM, WebvizPluginABC.StorageType.SESSION ) + # Adding each tab as a view element self.add_view( OverviewTabLayout(self._datamodel), PlugInIDs.SwatinitViews.OVERVIEW, @@ -75,12 +76,12 @@ def __init__( self.add_view( TabQqPlotLayout(self._datamodel), PlugInIDs.SwatinitViews.WATER, - PlugInIDs.SwatinitViews.GROUP_NAME + PlugInIDs.SwatinitViews.GROUP_NAME, ) self.add_view( TabMaxPcInfoLayout(self._datamodel), PlugInIDs.SwatinitViews.WATER, - PlugInIDs.SwatinitViews.GROUP_NAME + PlugInIDs.SwatinitViews.GROUP_NAME, ) @property @@ -89,4 +90,3 @@ def layout(self) -> wcc.Tabs: def add_webvizstore(self) -> List[Tuple[Callable, List[dict]]]: return self._datamodel.webviz_store - diff --git a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py index fdeebfb87..609773d89 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py +++ b/webviz_subsurface/plugins/_swatinit_qc/_plugin_ids.py @@ -1,23 +1,18 @@ +# pylint: disable=too-few-public-methods class PlugInIDs: - # pylint: disable=too-few-public-methods class Tabs: - # pylint: disable=too-few-public-methods QC_PLOTS = "Water Initializaion QC plots" MAX_PC_SCALING = "Capillary pressure scaling" OVERVIEW = "Overview and Information" class Stores: - # pylint: disable=too-few-public-methods class Shared: - # pylint: disable=too-few-public-methods PICK_VIEW = "pick-view" class Overview: - # pylint: disable=too-few-public-methods BUTTON = "button" class Water: - # pylint: disable=too-few-public-methods QC_VIZ = "qc-viz" EQLNUM = "eqlnum" COLOR_BY = "color_by" @@ -26,25 +21,21 @@ class Water: SATNUM = "satnum" class Capilary: - # pylint: disable=too-few-public-methods SPLIT_TABLE_BY = "split-table-by" MAX_PC_SCALE = "max-pc-scale" EQLNUM = "eqlnum" class SharedSettings: - # pylint: disable=too-few-public-methods PICK_VIEW = "pick-view" FILTERS = "filters" class SettingsGroups: - # pylint: disable=too-few-public-methods WATER_SEELECTORS = "water-selectors" WATER_FILTERS = "water-filters" CAPILAR_SELECTORS = "capilar-selectors" CAPILAR_FILTERS = "capilar-filters" class QcFlags: - # pylint: disable=too-few-public-methods FINE_EQUIL = "FINE_EQUIL" HC_BELOW_FWL = "HC_BELOW_FWL" PC_SCALED = "PC_SCALED" @@ -55,7 +46,6 @@ class QcFlags: WATER = "WATER" class SwatinitViews: - # pylint: disable=too-few-public-methods GROUP_NAME = "swatinit-group" OVERVIEW = "overview" diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py index d7e57ed74..435f39c52 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/__init__.py @@ -4,4 +4,6 @@ from ._layout_style import LayoutStyle from ._map_figure import MapFigure from ._overview_layout import OverviewViewelement +from ._properties_vs_depth import PropertiesVsDepthSubplots from ._water_layout import WaterViewelement +from ._waterfall_plot import WaterfallPlot diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py index a9c44a450..de3e313df 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_capilar_layout.py @@ -9,12 +9,14 @@ from webviz_config.webviz_plugin_subclasses import ViewElementABC from .._swatint import SwatinitQcDataModel -from ..settings_groups import CapilarFilters +from ..views.capilar_tab.settings import CapilarFilters from ._dash_table import DashTable from ._fullscreen import FullScreen from ._layout_style import LayoutStyle from ._map_figure import MapFigure +# This can be moved into a view_elements folder in views > capilar_tab + class CapilarViewelement(ViewElementABC): """All elements visible in the 'Caplillary pressure scaling'-tab @@ -34,17 +36,20 @@ def __init__( self.datamodel = datamodel self.init_eqlnums = self.datamodel.eqlnums[:1] - continous_filters = ([ - self.datamodel.dframe[col].min(), - self.datamodel.dframe[col].max() - ] for col in self.datamodel.filters_continuous) - - continous_filters_ids = ([ - { - "id": CapilarFilters.IDs.RANGE_FILTERS, - "col": col, - } - ] for col in self.datamodel.filters_continuous) + continous_filters = ( + [self.datamodel.dframe[col].min(), self.datamodel.dframe[col].max()] + for col in self.datamodel.filters_continuous + ) + + continous_filters_ids = ( + [ + { + "id": CapilarFilters.IDs.RANGE_FILTERS, + "col": col, + } + ] + for col in self.datamodel.filters_continuous + ) print({"EQLNUM": self.init_eqlnums}) self.dframe = self.datamodel.get_dataframe( diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py index af6c8ccf1..fa907e1fb 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_overview_layout.py @@ -7,6 +7,8 @@ from ._dash_table import DashTable from ._layout_style import LayoutStyle +# This can be moved into a view_elements folder in views > overview_tab + class OverviewViewelement(ViewElementABC): """All elements visible in the 'Overview and Information'-tab diff --git a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py index 63ad7c16c..cf8d0265c 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py +++ b/webviz_subsurface/plugins/_swatinit_qc/view_elements/_water_layout.py @@ -8,6 +8,8 @@ from ._fullscreen import FullScreen from ._layout_style import LayoutStyle +# This can be moved into a view_elements folder in views > water_tab + class WaterViewelement(ViewElementABC): """All elements visible in the 'Water Initialization QC plots'-tab @@ -30,7 +32,7 @@ def __init__( ) -> None: super().__init__() self.datamodel = datamodel - + self.main_figure = main_figure self.map_figure = map_figure self.qc_volumes = qc_volumes @@ -75,24 +77,29 @@ def info_box(self) -> html.Div: [ wcc.Header("Information about selection", style=LayoutStyle.HEADER), html.Div( - "EQLNUMS:", + "EQLNUMS:", style={"font-weight": "bold", "font-size": "15px"}, ), html.Div( ", ".join([str(x) for x in qc_vols["EQLNUMS"]]), - id=self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_EQLNUMS), + id=self.register_component_unique_id( + WaterViewelement.IDs.INFO_BOX_EQLNUMS ), + ), html.Div( "SATNUMS:", style={"font-weight": "bold", "font-size": "15px"}, ), html.Div( ", ".join([str(x) for x in qc_vols["SATNUMS"]]), - id= self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_SATNUMS), + id=self.register_component_unique_id( + WaterViewelement.IDs.INFO_BOX_SATNUMS + ), ), html.Div( html.Span("Reservoir Volume Difference:"), - style={"font-weight": "bold", "margin-top": "10px"}, ), + style={"font-weight": "bold", "margin-top": "10px"}, + ), html.Div( children=[ html.Div(line) @@ -102,7 +109,9 @@ def info_box(self) -> html.Div: f"HC Volume Diff (%): {qc_vols['HCVOL_DIFF_PERCENT']:.2f}", ] ], - id=self.register_component_unique_id(WaterViewelement.IDs.INFO_BOX_VOL_DIFF) + id=self.register_component_unique_id( + WaterViewelement.IDs.INFO_BOX_VOL_DIFF + ), ), ], style={"height": height, "padding": "10px"}, diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py index c34d4c251..d47bb7016 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/__init__.py @@ -1,3 +1,3 @@ -from ._capilary_preassure import TabMaxPcInfoLayout -from ._overview_info import OverviewTabLayout -from ._water_initialization import TabQqPlotLayout +from .capilar_tab import CapilarFilters, CapilarSelections, TabMaxPcInfoLayout +from .overbiew_tab import OverviewTabLayout +from .water_tab import TabQqPlotLayout, WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/__init__.py new file mode 100644 index 000000000..bf186e2d4 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/__init__.py @@ -0,0 +1,2 @@ +from ._capilary_preassure import TabMaxPcInfoLayout +from .settings import CapilarFilters, CapilarSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/_capilary_preassure.py similarity index 68% rename from webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py rename to webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/_capilary_preassure.py index 2ec2ac891..7baa0e435 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_capilary_preassure.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/_capilary_preassure.py @@ -1,12 +1,12 @@ from typing import Dict, List, Optional -from dash import ALL, Input, Output, State, callback, html +from dash import ALL, Input, Output, State, callback from webviz_config.webviz_plugin_subclasses import ViewABC -from .._plugin_ids import PlugInIDs -from .._swatint import SwatinitQcDataModel -from ..settings_groups import CapilarFilters, CapilarSelections -from ..view_elements import CapilarViewelement, MapFigure +from ..._plugin_ids import PlugInIDs +from ..._swatint import SwatinitQcDataModel +from ...view_elements import CapilarViewelement, MapFigure +from .settings import CapilarFilters, CapilarSelections class TabMaxPcInfoLayout(ViewABC): @@ -30,8 +30,13 @@ def __init__( TabMaxPcInfoLayout.IDs.CAPILAR_TAB, ) - self.add_settings_group(CapilarSelections(self.datamodel), PlugInIDs.SettingsGroups.CAPILAR_SELECTORS) - self.add_settings_group(CapilarFilters(self.datamodel), PlugInIDs.SettingsGroups.CAPILAR_FILTERS) + self.add_settings_group( + CapilarSelections(self.datamodel), + PlugInIDs.SettingsGroups.CAPILAR_SELECTORS, + ) + self.add_settings_group( + CapilarFilters(self.datamodel), PlugInIDs.SettingsGroups.CAPILAR_FILTERS + ) def set_callbacks(self) -> None: # update map @@ -42,10 +47,7 @@ def set_callbacks(self) -> None: .to_string(), "figure", ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), - "data" - ), + Input(self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), "data"), Input( self.get_store_unique_id(PlugInIDs.Stores.Capilary.MAX_PC_SCALE), "data", @@ -54,8 +56,8 @@ def set_callbacks(self) -> None: { "id": self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) .component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) - .to_string(), # litt usikker på denne...? - "col": ALL + .to_string(), # litt usikker på denne...? + "col": ALL, }, "value", ), @@ -64,16 +66,16 @@ def set_callbacks(self) -> None: "id": self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) .component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) .to_string(), - "col": ALL + "col": ALL, }, "id", ), ) def _update_map( - eqlnums: list, - threshold: Optional[int], - continous_filters: List[List[str]], - continous_filters_ids: List[Dict[str, str]] + eqlnums: list, + threshold: Optional[int], + continous_filters: List[List[str]], + continous_filters_ids: List[Dict[str, str]], ) -> MapFigure: df = self.datamodel.get_dataframe( filters={"EQLNUM": eqlnums}, @@ -82,7 +84,7 @@ def _update_map( df_for_map = df[df["PC_SCALING"] >= threshold] if threshold is None: df_for_map = self.datamodel.resample_dataframe(df, max_points=10000) - + return MapFigure( dframe=df_for_map, color_by="EQLNUM", @@ -96,7 +98,7 @@ def _update_map( self.view_element(TabMaxPcInfoLayout.IDs.CAPILAR_TAB) .component_unique_id(CapilarViewelement.IDs.TABLE) .to_string(), - "columns" + "columns", ), Input( self.get_store_unique_id(PlugInIDs.Stores.Capilary.MAX_PC_SCALE), @@ -104,24 +106,15 @@ def _update_map( ), Input( self.get_store_unique_id(PlugInIDs.Stores.Capilary.SPLIT_TABLE_BY), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), - "data" + "data", ), + Input(self.get_store_unique_id(PlugInIDs.Stores.Capilary.EQLNUM), "data"), Input( - { - "id": CapilarFilters.IDs.RANGE_FILTERS, - "col": ALL - }, + {"id": CapilarFilters.IDs.RANGE_FILTERS, "col": ALL}, "value", ), State( - { - "id": CapilarFilters.IDs.RANGE_FILTERS, - "col": ALL - }, + {"id": CapilarFilters.IDs.RANGE_FILTERS, "col": ALL}, "id", ), ) @@ -129,20 +122,22 @@ def _update_table( threshold: Optional[list], groupby_eqlnum: list, eqlnums: list, - continous_filters: List[List[str]], - continous_filters_ids: List[Dict[str, str]] + continous_filters: List[List[str]], + continous_filters_ids: List[Dict[str, str]], ) -> List[dict]: df = self.datamodel.get_dataframe( filters={"EQLNUM": eqlnums}, range_filters=zip_filters(continous_filters, continous_filters_ids), ) - dframe = self.datamodel.get_max_pc_info_and_percent_for_data_matching_condition( - dframe=df, - condition=threshold, - groupby_eqlnum=groupby_eqlnum == "both", + dframe = ( + self.datamodel.get_max_pc_info_and_percent_for_data_matching_condition( + dframe=df, + condition=threshold, + groupby_eqlnum=groupby_eqlnum == "both", + ) ) - text_columns=self.selectors - columns=[ + text_columns = self.selectors + columns = [ { "name": i, "id": i, @@ -155,5 +150,4 @@ def _update_table( def zip_filters(filter_values: list, filter_ids: list) -> dict: - print(zip(filter_values, filter_ids)) return {id_val["col"]: values for values, id_val in zip(filter_values, filter_ids)} diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/__init__.py similarity index 52% rename from webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py rename to webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/__init__.py index 7c112e759..6866d83b8 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/__init__.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/__init__.py @@ -1,2 +1 @@ from ._caplilar_settings import CapilarFilters, CapilarSelections -from ._water_settings import WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/_caplilar_settings.py similarity index 96% rename from webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py rename to webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/_caplilar_settings.py index 480a1c805..bcb19b096 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_caplilar_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/capilar_tab/settings/_caplilar_settings.py @@ -6,8 +6,8 @@ from dash.development.base_component import Component from webviz_config.webviz_plugin_subclasses import SettingsGroupABC -from .._plugin_ids import PlugInIDs -from .._swatint import SwatinitQcDataModel +from ...._plugin_ids import PlugInIDs +from ...._swatint import SwatinitQcDataModel class CapilarSelections(SettingsGroupABC): @@ -80,7 +80,9 @@ class IDs: def __init__(self, datamodel: SwatinitQcDataModel) -> None: super().__init__("Filter") self.datamodel = datamodel - self.range_filters_id = self.register_component_unique_id(CapilarFilters.IDs.RANGE_FILTERS) + self.range_filters_id = self.register_component_unique_id( + CapilarFilters.IDs.RANGE_FILTERS + ) def layout(self) -> List[Component]: return [ diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/__init__.py new file mode 100644 index 000000000..98c6f3689 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/__init__.py @@ -0,0 +1 @@ +from ._overview_info import OverviewTabLayout diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py b/webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/_overview_info.py similarity index 92% rename from webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py rename to webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/_overview_info.py index 7a83c8ec9..1e3686224 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_overview_info.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/overbiew_tab/_overview_info.py @@ -3,9 +3,9 @@ from dash.exceptions import PreventUpdate from webviz_config.webviz_plugin_subclasses import ViewABC -from .._plugin_ids import PlugInIDs -from .._swatint import SwatinitQcDataModel -from ..view_elements import OverviewViewelement +from ..._plugin_ids import PlugInIDs +from ..._swatint import SwatinitQcDataModel +from ...view_elements import OverviewViewelement class OverviewTabLayout(ViewABC): diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/__init__.py new file mode 100644 index 000000000..c4f38d69d --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/__init__.py @@ -0,0 +1,2 @@ +from ._water_initialization import TabQqPlotLayout +from .settings import WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/_water_initialization.py similarity index 60% rename from webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py rename to webviz_subsurface/plugins/_swatinit_qc/views/water_tab/_water_initialization.py index 7042465ee..2088ddfce 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/views/_water_initialization.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/_water_initialization.py @@ -1,14 +1,18 @@ -from typing import Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import plotly.graph_objects as go -from dash import Input, Output, State, callback, callback_context +from dash import ALL, Input, Output, State, callback, callback_context from webviz_config.webviz_plugin_subclasses import ViewABC -from .._plugin_ids import PlugInIDs -from .._swatint import SwatinitQcDataModel -from ..settings_groups import WaterFilters, WaterSelections -from ..view_elements import WaterViewelement -from webviz_subsurface.plugins._swatinit_qc import view_elements +from ..._plugin_ids import PlugInIDs +from ..._swatint import SwatinitQcDataModel +from ...view_elements import ( + MapFigure, + PropertiesVsDepthSubplots, + WaterfallPlot, + WaterViewelement, +) +from .settings import WaterFilters, WaterSelections class TabQqPlotLayout(ViewABC): @@ -24,8 +28,7 @@ def __init__( super().__init__("Water Initialization QC plots") self.datamodel = datamodel - - + # Need to define these quanitities for the inital case self.main_figure = main_figure self.map_figure = map_figure self.qc_volumes = qc_volumes @@ -39,121 +42,97 @@ def __init__( TabQqPlotLayout.IDs.WATER_TAB, ) - self.add_settings_group(WaterSelections(self.datamodel), PlugInIDs.SettingsGroups.WATER_SEELECTORS) - self.add_settings_group(WaterFilters(self.datamodel), PlugInIDs.SettingsGroups.WATER_FILTERS) - + self.add_settings_group( + WaterSelections(self.datamodel), PlugInIDs.SettingsGroups.WATER_SEELECTORS + ) + self.add_settings_group( + WaterFilters(self.datamodel), PlugInIDs.SettingsGroups.WATER_FILTERS + ) def set_callbacks(self) -> None: - # update + # update @callback( Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) .to_string(), - "figure" + "figure", ), Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) .to_string(), - "figure" + "figure", ), Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.INFO_BOX_EQLNUMS) .to_string(), - "children" + "children", ), Output( - self.view_elements(TabQqPlotLayout.IDs.WATER_TAB) - .component_unique_id(WaterViewelement.IDs.INFO_BOX_SATNUMS), - "children" + self.view_elements(TabQqPlotLayout.IDs.WATER_TAB).component_unique_id( + WaterViewelement.IDs.INFO_BOX_SATNUMS + ), + "children", ), Output( - self.view_elements(TabQqPlotLayout.IDs.WATER_TAB) - .component_unique_id(WaterViewelement.IDs.INFO_BOX_VOL_DIFF), - "children" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.QC_VIZ), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.COLOR_BY), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.MAX_POINTS), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.QC_FLAG), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.SATNUM), - "data" - ), - Input( - self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), - "data" - ), - - Input({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "value"), - Input({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "value"), - State({"id": get_uuid(LayoutElements.FILTERS_DISCRETE), "col": ALL}, "id"), - State({"id": get_uuid(LayoutElements.FILTERS_CONTINOUS), "col": ALL}, "id"), + self.view_elements(TabQqPlotLayout.IDs.WATER_TAB).component_unique_id( + WaterViewelement.IDs.INFO_BOX_VOL_DIFF + ), + "children", + ), + Input(self.get_store_unique_id(PlugInIDs.Stores.Water.QC_VIZ), "data"), + Input(self.get_store_unique_id(PlugInIDs.Stores.Water.EQLNUM), "data"), + Input(self.get_store_unique_id(PlugInIDs.Stores.Water.COLOR_BY), "data"), + Input(self.get_store_unique_id(PlugInIDs.Stores.Water.MAX_POINTS), "data"), + Input({"id": WaterFilters.range_filters_id, "col": ALL}, "value"), + Input({"id": WaterFilters.descreate_filter_id, "col": ALL}, "value"), + State({"id": WaterFilters.range_filters_id, "col": ALL}, "id"), + State({"id": WaterFilters.descreate_filter_id, "col": ALL}, "id"), ) # pylint: disable=too-many-arguments def _update_plot( - tab_selected: str, + qc_viz: str, eqlnums: List[str], - dicrete_filters: List[List[str]], - continous_filters: List[List[str]], color_by: str, max_points: int, - plot_selector: str, - dicrete_filters_ids: List[Dict[str, str]], + continous_filters_val: List[List[str]], + descreate_filters_val: List[List[str]], continous_filters_ids: List[Dict[str, str]], + descreate_filters_ids: List[Dict[str, str]], ) -> list: - if tab_selected != Tabs.QC_PLOTS or max_points is None: - raise PreventUpdate - - filters = zip_filters(dicrete_filters, dicrete_filters_ids) + filters = zip_filters(descreate_filters_val, descreate_filters_ids) filters.update({"EQLNUM": eqlnums}) - df = datamodel.get_dataframe( + df = self.datamodel.get_dataframe( filters=filters, - range_filters=zip_filters(continous_filters, continous_filters_ids), + range_filters=zip_filters(continous_filters_val, continous_filters_ids), ) if df.empty: return ["No data left after filtering"] - qc_volumes = datamodel.compute_qc_volumes(df) + qc_volumes = self.datamodel.compute_qc_volumes(df) - df = datamodel.filter_dframe_on_depth(df) - df = datamodel.resample_dataframe(df, max_points=max_points) + df = self.datamodel.filter_dframe_on_depth(df) + df = self.datamodel.resample_dataframe(df, max_points=max_points) - colormap = datamodel.create_colormap(color_by) + colormap = self.datamodel.create_colormap(color_by) main_plot = ( WaterfallPlot(qc_vols=qc_volumes).figure - if plot_selector == qc_plot_layout.MainPlots.WATERFALL + if qc_viz == WaterSelections.Values.WATERFALL else PropertiesVsDepthSubplots( dframe=df, color_by=color_by, colormap=colormap, - discrete_color=color_by in datamodel.SELECTORS, + discrete_color=color_by in self.datamodel.SELECTORS, ).figure ) map_figure = MapFigure( dframe=df, color_by=color_by, - faultlinedf=datamodel.faultlines_df, + faultlinedf=self.datamodel.faultlines_df, colormap=colormap, ).figure @@ -161,51 +140,55 @@ def _update_plot( main_figure=main_plot, map_figure=map_figure, qc_volumes=qc_volumes, - ) - + ) # this must return something else + @callback( Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) .to_string(), - "figure" + "figure", ), Output( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) .to_string(), - "figure" + "figure", ), Input( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) .to_string(), - "selectedData" + "selectedData", ), Input( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) .to_string(), - "selectedData" + "selectedData", ), State( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAIN_FIGURE) .to_string(), - "figure" + "figure", ), State( self.view_element(TabQqPlotLayout.IDs.WATER_TAB) .component_unique_id(WaterViewelement.IDs.MAP_FIGURE) .to_string(), - "figure" + "figure", ), ) - def _update_selected_points_in_figure(selected_main: dict, selected_map: dict, mainfig: dict, mapfig: dict) -> Tuple[dict, dict]: + def _update_selected_points_in_figure( + selected_main: dict, selected_map: dict, mainfig: dict, mapfig: dict + ) -> Tuple[dict, dict]: ctx = callback_context.triggered[0]["prop_id"] selected = ( - selected_map if WaterViewelement.IDs.MAP_FIGURE in ctx else selected_main + selected_map + if WaterViewelement.IDs.MAP_FIGURE in ctx + else selected_main ) point_indexes = get_point_indexes_from_selected(selected) @@ -216,8 +199,9 @@ def _update_selected_points_in_figure(selected_main: dict, selected_map: dict, m return mainfig, mapfig - - def get_point_indexes_from_selected(selected: Optional[dict]) -> Union[list, dict]: + def get_point_indexes_from_selected( + selected: Optional[dict], + ) -> Union[list, dict]: if not (isinstance(selected, dict) and "points" in selected): return [] @@ -233,7 +217,6 @@ def get_point_indexes_from_selected(selected: Optional[dict]) -> Union[list, dic point_indexes[trace_name].append(point["pointNumber"]) return point_indexes - def update_selected_points_in_trace( trace: dict, point_indexes: Union[dict, list] ) -> None: @@ -243,4 +226,8 @@ def update_selected_points_in_trace( if isinstance(point_indexes, list) else point_indexes.get(trace["name"], []) ) - trace.update(selectedpoints=selectedpoints if point_indexes else None) \ No newline at end of file + trace.update(selectedpoints=selectedpoints if point_indexes else None) + + +def zip_filters(filter_values: list, filter_ids: list) -> dict: + return {id_val["col"]: values for values, id_val in zip(filter_values, filter_ids)} diff --git a/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/__init__.py b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/__init__.py new file mode 100644 index 000000000..fc900d6d1 --- /dev/null +++ b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/__init__.py @@ -0,0 +1 @@ +from ._water_settings import WaterFilters, WaterSelections diff --git a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/_water_settings.py similarity index 82% rename from webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py rename to webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/_water_settings.py index 14235ed9a..20ad65680 100644 --- a/webviz_subsurface/plugins/_swatinit_qc/settings_groups/_water_settings.py +++ b/webviz_subsurface/plugins/_swatinit_qc/views/water_tab/settings/_water_settings.py @@ -5,20 +5,23 @@ from dash.development.base_component import Component from webviz_config.webviz_plugin_subclasses import SettingsGroupABC -from .._plugin_ids import PlugInIDs -from .._swatint import SwatinitQcDataModel +from ...._plugin_ids import PlugInIDs +from ...._swatint import SwatinitQcDataModel class WaterSelections(SettingsGroupABC): class IDs: # pylint: disable=too-few-public-methods - WATERFALL = "waterfall" - PROP_VS_DEPTH = "prop-vs-depth" SELECT_QC = "select-qc" EQLNUM = "eqlnum" COLOR_BY = "color-by" MAX_POINTS = "max-points" + class Values: + # pylint: disable=too-few-public-methods + WATERFALL = "waterfall" + PROP_VS_DEPTH = "prop-vs-depth" + def __init__(self, datamodel: SwatinitQcDataModel) -> None: super().__init__("Selections") self.datamodel = datamodel @@ -31,14 +34,14 @@ def layout(self) -> List[Component]: options=[ { "label": "Waterfall plot for water vol changes", - "value": WaterSelections.IDs.WATERFALL, + "value": WaterSelections.Values.WATERFALL, }, { "label": "Reservoir properties vs Depth", - "value": WaterSelections.IDs.PROP_VS_DEPTH, + "value": WaterSelections.Values.PROP_VS_DEPTH, }, ], - value=WaterSelections.IDs.PROP_VS_DEPTH, + value=WaterSelections.Values.PROP_VS_DEPTH, clearable=False, ), wcc.SelectWithLabel( @@ -102,20 +105,24 @@ def _set_max_points(max: str) -> str: class WaterFilters(SettingsGroupABC): class IDs: # pylint: disable=too-few-public-methods - QC_FLAG = "qc-flag" - SATNUM = "satnum" + DESCREATE_FILTERS = "descreate-filters" RANGE_FILTERS = "range_filters" def __init__(self, datamodel) -> None: super().__init__("Filters") self.datamodel = datamodel - self.range_filters_id = self.register_component_unique_id(WaterFilters.IDs.RANGE_FILTERS) + self.descreate_fiters_id = self.register_component_unique_id( + WaterFilters.IDs.DESCREATE_FILTERS + ) + self.range_filters_id = self.register_component_unique_id( + WaterFilters.IDs.RANGE_FILTERS + ) def layout(self) -> List[Component]: return [ wcc.SelectWithLabel( label="QC_FLAG", - id=self.register_component_unique_id(WaterFilters.IDs.QC_FLAG), + id={"id": self.range_filters_id, "col": "qc-flag"}, options=[ {"label": ens, "value": ens} for ens in self.datamodel.qc_flag ], @@ -124,7 +131,7 @@ def layout(self) -> List[Component]: ), wcc.SelectWithLabel( label="SATNUM", - id=self.register_component_unique_id(WaterFilters.IDs.SATNUM), + id={"id": self.range_filters_id, "col": "satnum"}, options=[ {"label": ens, "value": ens} for ens in self.datamodel.satnums ], @@ -154,20 +161,3 @@ def range_filters(self) -> List: ) ) return filters - - def set_callbacks(self) -> None: - @callback( - Output(self.get_store_unique_id(PlugInIDs.Stores.Water.QC_FLAG), "data"), - Input( - self.component_unique_id(WaterFilters.IDs.QC_FLAG).to_string(), "value" - ), - ) - def _set_qc_flag(qc_flag: List[str]) -> List[str]: - return qc_flag - - @callback( - Output(self.get_store_unique_id(PlugInIDs.Stores.Water.SATNUM), "data"), - Input(self.component_unique_id(WaterFilters.IDs.SATNUM), "value"), - ) - def _set_satnum(satnum: List[int]) -> List[int]: - return satnum