Skip to content

Commit

Permalink
CO2Leakage - Visualize CO2 mass plus various other improvements (equi…
Browse files Browse the repository at this point in the history
…nor#1252)

* CO2Leakage: Minor visual fix in Property-tab.

* Black last commit.

* CO2Leakage: First iteration, adding code for co2 mass maps.

* CO2Leakage: Add some checks for existing files.

* CO2Leakage: Minor fix for mass maps.

* CO2Leakage: Plot unit for time migration and mass maps.

* Removal of simple volume

* Match ccs script naming (actual volume). Add space b4 well.

* Change input from ccs-scripts to plume_x

* Fix minor bug in space before well name hack

* Fix zoom level when updating plot

* Fix formatting of numbers and plot ranges

* Fix zoom bug that messed up scales

* Add visualization of dissolved and free CO2

* Add visualization threshold

* Show total mass in realization

* Small fix for zoom in containment plots

* Add zone selection for containment plot.

* Add zone functionality

* Allow relative well and boundary paths

* Use scientific notation for total mass

* Clean up code for relative path to well and boundaries

* Small fixes

* Fix handling of input files

* Add feedback button

* Clean up zone functionality

* Disable feedback button (for now)

* Minor fixes

* Fix black formating.

* Minor fix pylint.

* Various formatting fixes and small enhancements. (#5)

* Various pylint fixes.

* Minor fix black.

* Minor fixes, pylint and mypy.

* Various fixes, simplifications.

* Edit standard surface filenames and fix settings buttons (#6)

* Change expected surface filenames and add warning if missing

* Bug fix, reenable options button

* Make visualization threshold more intuitive

* Minor formatting fix

* Improve handling of update button n_clicks

* Improve handling of update button n_clicks (#7)

* Update CHANGELOG.md

* Add unit selection and improve visualization threshold

* Minor fix to map update handling

* Add region support for the containment plots

* Formatting

* CCS-88: Change default behavior of UNSMRY-file, print warning if large file (#8)

* CCS-88: Change default behavior of UNSMRY-file, only load and plot if specified.

* CCS-88: Print warning if some CSV-files are large.

* CCS-88: Minor fix.

* Make visualization updates more intuitive.

* Minor fix

* Add support for separate well pick and polygon files for each ensemble (requires same relative path).

* Minor formatting in formation-well warning.

* pylint

* mypy

* Update changelog.

---------

Co-authored-by: Audun Sektnan (SUB CC SIG) <ause@st-linrgsn321.st.statoil.no>
Co-authored-by: FredrikNevjenNR <fnevjen@nr.no>
Co-authored-by: FredrikNevjenNR <150343101+FredrikNevjenNR@users.noreply.github.com>
Co-authored-by: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com>
  • Loading branch information
5 people committed Mar 7, 2024
1 parent 184886a commit f3bbc33
Show file tree
Hide file tree
Showing 8 changed files with 1,178 additions and 230 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1244](https://github.com/equinor/webviz-subsurface/pull/1244) - New functionality in `VolumetricAnalysis` to compute facies fractions if `FACIES` is present in the volumetric table. Also added possibility to have labels on bar plots with user defined value format.
- [#1247](https://github.com/equinor/webviz-subsurface/pull/1247) - Added P10/P90 to the Uncertainty table in `StructuralUncertainty`.

### Changed
- [#1252](https://github.com/equinor/webviz-subsurface/pull/1252) - Added CO2 mass visualization to `CO2Leakage`, plus various other improvements: support for regions, option to plot containment split into zones, remove code for simple volume.

### Fixed
- [#1252](https://github.com/equinor/webviz-subsurface/pull/1252) - Fixed zoom resetting issue in `CO2Leakage`. Also some fixes to avoid crash for large UNSMRY CSV-files.

## [0.2.22] - 2023-08-31

Expand Down
289 changes: 199 additions & 90 deletions webviz_subsurface/plugins/_co2_leakage/_plugin.py

Large diffs are not rendered by default.

151 changes: 138 additions & 13 deletions webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
import plotly.graph_objects as go
import webviz_subsurface_components as wsc
from flask_caching import Cache

from webviz_subsurface._providers import (
EnsembleSurfaceProvider,
Expand All @@ -29,6 +30,8 @@
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
Co2MassScale,
Co2VolumeScale,
ContainmentViews,
GraphSource,
LayoutLabels,
MapAttribute,
)
Expand Down Expand Up @@ -73,8 +76,16 @@ def from_server(
color_map_range: Tuple[Optional[float], Optional[float]],
color_map_name: str,
readable_name_: str,
) -> "SurfaceData":
surf_meta, img_url = publish_and_get_surface_metadata(server, provider, address)
visualization_info: Dict[str, Any],
map_attribute_names: Dict[MapAttribute, str],
) -> Tuple[Any, Optional[Any]]:
surf_meta, img_url, summed_mass = publish_and_get_surface_metadata(
server,
provider,
address,
visualization_info,
map_attribute_names,
)
assert surf_meta is not None # Should not occur
value_range = (
0.0 if np.ma.is_masked(surf_meta.val_min) else surf_meta.val_min,
Expand All @@ -84,13 +95,16 @@ def from_server(
value_range[0] if color_map_range[0] is None else color_map_range[0],
value_range[1] if color_map_range[1] is None else color_map_range[1],
)
return SurfaceData(
readable_name_,
color_map_range,
color_map_name,
value_range,
surf_meta,
img_url,
return (
SurfaceData(
readable_name_,
color_map_range,
color_map_name,
value_range,
surf_meta,
img_url,
),
summed_mass,
)


Expand Down Expand Up @@ -183,10 +197,20 @@ def get_plume_polygon(
)


def _find_legend_title(attribute: MapAttribute, unit: str) -> str:
if attribute == MapAttribute.MIGRATION_TIME:
return "years"
if attribute in [MapAttribute.MASS, MapAttribute.DISSOLVED, MapAttribute.FREE]:
return unit
return ""


def create_map_annotations(
formation: str,
surface_data: Optional[SurfaceData],
colortables: List[Dict[str, Any]],
attribute: MapAttribute,
unit: str,
) -> List[wsc.ViewAnnotation]:
annotations = []
if surface_data is not None:
Expand All @@ -195,13 +219,14 @@ def create_map_annotations(
id="1_view",
children=[
wsc.WebVizColorLegend(
title=_find_legend_title(attribute, unit),
min=surface_data.color_map_range[0],
max=surface_data.color_map_range[1],
colorName=surface_data.color_map_name,
cssLegendStyles={"top": "0", "right": "0"},
openColorSelector=False,
legendScaleSize=0.1,
legendFontSize=30,
legendFontSize=20,
colorTables=colortables,
),
wsc.ViewFooter(children=formation),
Expand Down Expand Up @@ -305,11 +330,23 @@ def create_map_layers(
)
if (
well_pick_provider is not None
and formation is not None
and LayoutLabels.SHOW_WELLS in options_dialog_options
):
well_data = dict(well_pick_provider.get_geojson(selected_wells, formation))
if "features" in well_data and len(well_data["features"]) == 0:
warnings.warn(f'Formation name "{formation}" not found in well picks file.')
if "features" in well_data:
if len(well_data["features"]) == 0:
wellstring = "well: " if len(selected_wells) == 1 else "wells: "
wellstring += ", ".join(selected_wells)
warnings.warn(
f"Combination of formation: {formation} and "
f"{wellstring} not found in well picks file."
)
for i in range(len(well_data["features"])):
current_attribute = well_data["features"][i]["properties"]["attribute"]
well_data["features"][i]["properties"]["attribute"] = (
" " + current_attribute
)
layers.append(
{
"@@type": "GeoJsonLayer",
Expand Down Expand Up @@ -346,20 +383,23 @@ def generate_containment_figures(
co2_scale: Union[Co2MassScale, Co2VolumeScale],
realization: int,
y_limits: List[Optional[float]],
containment_info: Dict[str, Union[str, None, List[str]]],
) -> Tuple[go.Figure, go.Figure, go.Figure]:
try:
fig0 = generate_co2_volume_figure(
table_provider,
table_provider.realizations(),
co2_scale,
containment_info,
)
fig1 = generate_co2_time_containment_figure(
table_provider,
table_provider.realizations(),
co2_scale,
containment_info,
)
fig2 = generate_co2_time_containment_one_realization_figure(
table_provider, co2_scale, realization, y_limits
table_provider, co2_scale, realization, y_limits, containment_info
)
except KeyError as exc:
warnings.warn(f"Could not generate CO2 figures: {exc}")
Expand Down Expand Up @@ -415,3 +455,88 @@ def _parse_polygon_file(filename: str) -> Dict[str, Any]:
],
}
return as_geojson


def process_visualization_info(
n_clicks: int,
threshold: Optional[float],
unit: str,
stored_info: Dict[str, Any],
cache: Cache,
) -> Dict[str, Any]:
"""
Clear surface cache if the threshold for visualization or mass unit is changed
"""
stored_info["change"] = False
stored_info["n_clicks"] = n_clicks
if unit != stored_info["unit"]:
stored_info["unit"] = unit
stored_info["change"] = True
if threshold is not None and threshold != stored_info["threshold"]:
stored_info["threshold"] = threshold
stored_info["change"] = True
if stored_info["change"]:
cache.clear()
# stored_info["n_clicks"] = n_clicks
return stored_info


def process_containment_info(
zone: Optional[str],
region: Optional[str],
view: Optional[str],
zone_and_region_options: Dict[str, List[str]],
source: str,
) -> Dict[str, Union[str, None, List[str]]]:
zones = zone_and_region_options["zones"]
regions = zone_and_region_options["regions"]
if source in [
GraphSource.CONTAINMENT_MASS,
GraphSource.CONTAINMENT_ACTUAL_VOLUME,
]:
if view == ContainmentViews.CONTAINMENTSPLIT:
return {"zone": zone, "region": region, "containment_view": view}
if view == ContainmentViews.ZONESPLIT and len(zones) > 0:
zones = [zone_name for zone_name in zones if zone_name != "all"]
elif view == ContainmentViews.REGIONSPLIT and len(regions) > 0:
regions = [reg_name for reg_name in regions if reg_name != "all"]
else:
return {
"zone": zone,
"region": region,
"containment_view": ContainmentViews.CONTAINMENTSPLIT,
}
return {
"zone": zone,
"region": region,
"containment_view": view,
"zones": zones,
"regions": regions,
}
return {"containment_view": ContainmentViews.CONTAINMENTSPLIT}


def process_summed_mass(
formation: str,
realization: List[int],
datestr: str,
attribute: MapAttribute,
summed_mass: Optional[float],
surf_data: Optional[SurfaceData],
summed_co2: Dict[str, float],
unit: str,
) -> Tuple[Optional[SurfaceData], Dict[str, float]]:
summed_co2_key = f"{formation}-{realization[0]}-{datestr}-{attribute}-{unit}"
if len(realization) == 1:
if attribute in [
MapAttribute.MASS,
MapAttribute.DISSOLVED,
MapAttribute.FREE,
]:
if summed_mass is not None and summed_co2_key not in summed_co2:
summed_co2[summed_co2_key] = summed_mass
if summed_co2_key in summed_co2 and surf_data is not None:
surf_data.readable_name += (
f" ({unit}) (Total: {summed_co2[summed_co2_key]:.2E}): "
)
return surf_data, summed_co2
Loading

0 comments on commit f3bbc33

Please sign in to comment.