webviz-subsurface 0.2.36__py3-none-any.whl → 0.2.38__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. webviz_subsurface/__init__.py +1 -1
  2. webviz_subsurface/_components/color_picker.py +1 -1
  3. webviz_subsurface/_datainput/well_completions.py +2 -1
  4. webviz_subsurface/_providers/ensemble_polygon_provider/__init__.py +3 -0
  5. webviz_subsurface/_providers/ensemble_polygon_provider/_polygon_discovery.py +97 -0
  6. webviz_subsurface/_providers/ensemble_polygon_provider/_provider_impl_file.py +226 -0
  7. webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider.py +53 -0
  8. webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider_factory.py +99 -0
  9. webviz_subsurface/_providers/ensemble_polygon_provider/polygon_server.py +125 -0
  10. webviz_subsurface/plugins/_co2_leakage/_plugin.py +577 -293
  11. webviz_subsurface/plugins/_co2_leakage/_types.py +7 -0
  12. webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py +9 -0
  13. webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +226 -186
  14. webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +591 -128
  15. webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py +147 -0
  16. webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py +31 -0
  17. webviz_subsurface/plugins/_co2_leakage/_utilities/ensemble_well_picks.py +105 -0
  18. webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +170 -2
  19. webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +199 -97
  20. webviz_subsurface/plugins/_co2_leakage/_utilities/polygon_handler.py +60 -0
  21. webviz_subsurface/plugins/_co2_leakage/_utilities/summary_graphs.py +77 -173
  22. webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +122 -21
  23. webviz_subsurface/plugins/_co2_leakage/_utilities/unsmry_data_provider.py +108 -0
  24. webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +44 -19
  25. webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +944 -359
  26. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/METADATA +2 -2
  27. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/RECORD +33 -20
  28. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/WHEEL +1 -1
  29. /webviz_subsurface/plugins/_co2_leakage/_utilities/{fault_polygons.py → fault_polygons_handler.py} +0 -0
  30. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE +0 -0
  31. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE.chromedriver +0 -0
  32. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/entry_points.txt +0 -0
  33. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
- import glob
2
1
  import logging
3
2
  import os
4
3
  import warnings
5
4
  from pathlib import Path
6
5
  from typing import Dict, List, Optional
7
6
 
7
+ from fmu.ensemble import ScratchEnsemble
8
8
  from webviz_config import WebvizSettings
9
9
 
10
10
  from webviz_subsurface._providers import (
@@ -13,37 +13,87 @@ from webviz_subsurface._providers import (
13
13
  EnsembleTableProvider,
14
14
  EnsembleTableProviderFactory,
15
15
  )
16
- from webviz_subsurface._utils.webvizstore_functions import read_csv
17
- from webviz_subsurface.plugins._co2_leakage._utilities.co2volume import (
18
- read_menu_options,
16
+ from webviz_subsurface._providers.ensemble_polygon_provider import PolygonServer
17
+ from webviz_subsurface._providers.ensemble_surface_provider._surface_discovery import (
18
+ discover_per_realization_surface_files,
19
+ )
20
+ from webviz_subsurface.plugins._co2_leakage._utilities.containment_data_provider import (
21
+ ContainmentDataProvider,
22
+ )
23
+ from webviz_subsurface.plugins._co2_leakage._utilities.ensemble_well_picks import (
24
+ EnsembleWellPicks,
19
25
  )
20
26
  from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
27
+ BoundarySettings,
28
+ FilteredMapAttribute,
21
29
  GraphSource,
22
30
  MapAttribute,
31
+ MapNamingConvention,
32
+ MenuOptions,
23
33
  )
24
- from webviz_subsurface.plugins._map_viewer_fmu._tmp_well_pick_provider import (
25
- WellPickProvider,
34
+ from webviz_subsurface.plugins._co2_leakage._utilities.polygon_handler import (
35
+ PolygonHandler,
36
+ )
37
+ from webviz_subsurface.plugins._co2_leakage._utilities.unsmry_data_provider import (
38
+ UnsmryDataProvider,
26
39
  )
27
40
 
28
41
  LOGGER = logging.getLogger(__name__)
42
+ LOGGER_TO_SUPPRESS = logging.getLogger(
43
+ "webviz_subsurface._providers.ensemble_summary_provider._arrow_unsmry_import"
44
+ )
45
+ LOGGER_TO_SUPPRESS.setLevel(logging.ERROR) # We replace the given warning with our own
29
46
  WARNING_THRESHOLD_CSV_FILE_SIZE_MB = 100.0
30
47
 
31
48
 
49
+ def build_mapping(
50
+ webviz_settings: WebvizSettings,
51
+ ensembles: List[str],
52
+ ) -> Dict[str, str]:
53
+ available_attrs_per_ensemble = [
54
+ discover_per_realization_surface_files(
55
+ webviz_settings.shared_settings["scratch_ensembles"][ens],
56
+ "share/results/maps",
57
+ )
58
+ for ens in ensembles
59
+ ]
60
+ full_attr_list = [
61
+ [attr.attribute for attr in ens] for ens in available_attrs_per_ensemble
62
+ ]
63
+ unique_attributes = set()
64
+ for ens_attr in full_attr_list:
65
+ unique_attributes.update(ens_attr)
66
+ unique_attributes_list = list(unique_attributes)
67
+ mapping = {}
68
+ for attr in unique_attributes_list:
69
+ for name_convention in MapNamingConvention:
70
+ if attr == name_convention.value:
71
+ attribute_key = MapAttribute[name_convention.name].name
72
+ mapping[attribute_key] = attr
73
+ break
74
+ return mapping
75
+
76
+
32
77
  def init_map_attribute_names(
33
- mapping: Optional[Dict[str, str]]
34
- ) -> Dict[MapAttribute, str]:
35
- if mapping is None:
36
- # Based on name convention of xtgeoapp_grd3dmaps:
37
- return {
38
- MapAttribute.MIGRATION_TIME_SGAS: "migrationtime_sgas",
39
- MapAttribute.MIGRATION_TIME_AMFG: "migrationtime_amfg",
40
- MapAttribute.MAX_SGAS: "max_sgas",
41
- MapAttribute.MAX_AMFG: "max_amfg",
42
- MapAttribute.MASS: "co2-mass-total",
43
- MapAttribute.DISSOLVED: "co2-mass-aqu-phase",
44
- MapAttribute.FREE: "co2-mass-gas-phase",
45
- }
46
- return {MapAttribute[key]: value for key, value in mapping.items()}
78
+ webviz_settings: WebvizSettings,
79
+ ensembles: List[str],
80
+ input_mapping: Optional[Dict[str, str]],
81
+ ) -> FilteredMapAttribute:
82
+ default_mapping = build_mapping(webviz_settings, ensembles)
83
+ final_mapping = dict(default_mapping)
84
+ if input_mapping is not None:
85
+ for key, value in input_mapping.items():
86
+ if key in final_mapping and final_mapping[key] != value:
87
+ LOGGER.info(
88
+ f"Conflict on attribute '{key}': prioritizing '{value}' (from input attributes)"
89
+ f" over '{final_mapping[key]}' (from default attributes)"
90
+ )
91
+ final_mapping[key] = value
92
+ final_attributes = {
93
+ (MapAttribute[key].value if key in MapAttribute.__members__ else key): value
94
+ for key, value in final_mapping.items()
95
+ }
96
+ return FilteredMapAttribute(final_attributes)
47
97
 
48
98
 
49
99
  def init_surface_providers(
@@ -60,105 +110,157 @@ def init_surface_providers(
60
110
 
61
111
 
62
112
  def init_well_pick_provider(
63
- well_pick_dict: Dict[str, Optional[str]],
113
+ ensemble_paths: Dict[str, str],
114
+ well_pick_path: Optional[str],
64
115
  map_surface_names_to_well_pick_names: Optional[Dict[str, str]],
65
- ) -> Dict[str, Optional[WellPickProvider]]:
66
- well_pick_provider: Dict[str, Optional[WellPickProvider]] = {}
67
- ensembles = list(well_pick_dict.keys())
68
- for ens in ensembles:
69
- well_pick_path = well_pick_dict[ens]
70
- if well_pick_path is None:
71
- well_pick_provider[ens] = None
72
- else:
73
- try:
74
- well_pick_provider[ens] = WellPickProvider(
75
- read_csv(well_pick_path), map_surface_names_to_well_pick_names
76
- )
77
- except OSError:
78
- well_pick_provider[ens] = None
79
- return well_pick_provider
116
+ ) -> Dict[str, EnsembleWellPicks]:
117
+ if well_pick_path is None:
118
+ return {}
80
119
 
120
+ return {
121
+ ens: EnsembleWellPicks(
122
+ ens_p, well_pick_path, map_surface_names_to_well_pick_names
123
+ )
124
+ for ens, ens_p in ensemble_paths.items()
125
+ }
81
126
 
82
- def init_table_provider(
127
+
128
+ def init_polygon_provider_handlers(
129
+ server: PolygonServer,
130
+ ensemble_paths: Dict[str, str],
131
+ options: Optional[BoundarySettings],
132
+ ) -> Dict[str, PolygonHandler]:
133
+ filled_options: BoundarySettings = {
134
+ "polygon_file_pattern": "share/results/polygons/*.csv",
135
+ "attribute": "boundary",
136
+ "hazardous_name": "hazardous",
137
+ "containment_name": "containment",
138
+ }
139
+ if options is not None:
140
+ filled_options.update(options)
141
+ return {
142
+ ens: PolygonHandler(
143
+ server,
144
+ ens_path,
145
+ filled_options,
146
+ )
147
+ for ens, ens_path in ensemble_paths.items()
148
+ }
149
+
150
+
151
+ def init_unsmry_data_providers(
83
152
  ensemble_roots: Dict[str, str],
84
- table_rel_path: str,
85
- ) -> Dict[str, EnsembleTableProvider]:
86
- providers = {}
153
+ table_rel_path: Optional[str],
154
+ ) -> Dict[str, UnsmryDataProvider]:
155
+ if table_rel_path is None:
156
+ return {}
87
157
  factory = EnsembleTableProviderFactory.instance()
88
- for ens, ens_path in ensemble_roots.items():
89
- max_size_mb = _find_max_file_size_mb(ens_path, table_rel_path)
90
- if max_size_mb > WARNING_THRESHOLD_CSV_FILE_SIZE_MB:
91
- text = (
92
- "Some CSV-files are very large and might create problems when loading."
93
- )
94
- text += f"\n ensembles: {ens}"
95
- text += f"\n CSV-files: {table_rel_path}"
96
- text += f"\n Max size : {max_size_mb:.2f} MB"
97
- LOGGER.warning(text)
158
+ providers = {
159
+ ens: _init_ensemble_table_provider(factory, ens, ens_path, table_rel_path)
160
+ for ens, ens_path in ensemble_roots.items()
161
+ }
162
+ return {k: UnsmryDataProvider(v) for k, v in providers.items() if v is not None}
163
+
164
+
165
+ def init_containment_data_providers(
166
+ ensemble_roots: Dict[str, str],
167
+ table_rel_path: Optional[str],
168
+ ) -> Dict[str, ContainmentDataProvider]:
169
+ if table_rel_path is None:
170
+ return {}
171
+ factory = EnsembleTableProviderFactory.instance()
172
+ providers = {
173
+ ens: _init_ensemble_table_provider(factory, ens, ens_path, table_rel_path)
174
+ for ens, ens_path in ensemble_roots.items()
175
+ }
176
+ return {
177
+ k: ContainmentDataProvider(v) for k, v in providers.items() if v is not None
178
+ }
98
179
 
180
+
181
+ def _init_ensemble_table_provider(
182
+ factory: EnsembleTableProviderFactory,
183
+ ens: str,
184
+ ens_path: str,
185
+ table_rel_path: str,
186
+ ) -> Optional[EnsembleTableProvider]:
187
+ try:
188
+ return factory.create_from_per_realization_arrow_file(ens_path, table_rel_path)
189
+ except (KeyError, ValueError) as exc:
99
190
  try:
100
- providers[ens] = factory.create_from_per_realization_csv_file(
191
+ return factory.create_from_per_realization_csv_file(
101
192
  ens_path, table_rel_path
102
193
  )
103
- except (KeyError, ValueError) as exc:
194
+ except (KeyError, ValueError) as exc2:
104
195
  LOGGER.warning(
105
- f'Did not load "{table_rel_path}" for ensemble "{ens}" with error {exc}'
196
+ f'\nTried reading "{table_rel_path}" for ensemble "{ens}" as csv with'
197
+ f" error \n- {exc2}, \nand as arrow with error \n- {exc}"
106
198
  )
107
- return providers
199
+ return None
108
200
 
109
201
 
110
- def _find_max_file_size_mb(ens_path: str, table_rel_path: str) -> float:
111
- glob_pattern = os.path.join(ens_path, table_rel_path)
112
- paths = glob.glob(glob_pattern)
113
- max_size = 0.0
114
- for file in paths:
115
- if os.path.exists(file):
116
- file_stats = os.stat(file)
117
- size_in_mb = file_stats.st_size / (1024 * 1024)
118
- max_size = max(max_size, size_in_mb)
119
- return max_size
202
+ def init_realizations(ensemble_paths: Dict[str, str]) -> Dict[str, List[int]]:
203
+ realization_per_ens = {}
204
+ for ens, ens_path in ensemble_paths.items():
205
+ scratch_ensemble = ScratchEnsemble("dummyEnsembleName", paths=ens_path).filter(
206
+ "OK"
207
+ )
208
+ realization_per_ens[ens] = sorted(list(scratch_ensemble.realizations.keys()))
209
+ return realization_per_ens
120
210
 
121
211
 
122
212
  def init_menu_options(
123
213
  ensemble_roots: Dict[str, str],
124
- mass_table: Dict[str, EnsembleTableProvider],
125
- actual_volume_table: Dict[str, EnsembleTableProvider],
126
- mass_relpath: str,
127
- volume_relpath: str,
128
- ) -> Dict[str, Dict[str, Dict[str, List[str]]]]:
129
- options: Dict[str, Dict[str, Dict[str, List[str]]]] = {}
214
+ mass_table: Dict[str, ContainmentDataProvider],
215
+ actual_volume_table: Dict[str, ContainmentDataProvider],
216
+ unsmry_providers: Dict[str, UnsmryDataProvider],
217
+ ) -> Dict[str, Dict[GraphSource, MenuOptions]]:
218
+ options: Dict[str, Dict[GraphSource, MenuOptions]] = {}
130
219
  for ens in ensemble_roots.keys():
131
220
  options[ens] = {}
132
- for source, table, relpath in zip(
133
- [GraphSource.CONTAINMENT_MASS, GraphSource.CONTAINMENT_ACTUAL_VOLUME],
134
- [mass_table, actual_volume_table],
135
- [mass_relpath, volume_relpath],
136
- ):
137
- real = table[ens].realizations()[0]
138
- options[ens][source] = read_menu_options(table[ens], real, relpath)
139
- options[ens][GraphSource.UNSMRY] = {
140
- "zones": [],
141
- "regions": [],
142
- "phases": ["total", "gas", "aqueous"],
143
- }
221
+ if ens in mass_table:
222
+ options[ens][GraphSource.CONTAINMENT_MASS] = mass_table[ens].menu_options
223
+ if ens in actual_volume_table:
224
+ options[ens][GraphSource.CONTAINMENT_ACTUAL_VOLUME] = actual_volume_table[
225
+ ens
226
+ ].menu_options
227
+ if ens in unsmry_providers:
228
+ options[ens][GraphSource.UNSMRY] = unsmry_providers[ens].menu_options
144
229
  return options
145
230
 
146
231
 
147
- def process_files(
148
- cont_bound: Optional[str],
149
- haz_bound: Optional[str],
150
- well_file: Optional[str],
151
- ensemble_paths: Dict[str, str],
152
- ) -> List[Dict[str, Optional[str]]]:
153
- """
154
- Checks if the files exist (otherwise gives a warning and returns None)
155
- Concatenates ensemble root dir and path to file if relative
156
- """
157
- ensembles = list(ensemble_paths.keys())
158
- return [
159
- {ens: _process_file(source, ensemble_paths[ens]) for ens in ensembles}
160
- for source in [cont_bound, haz_bound, well_file]
161
- ]
232
+ def init_dictionary_of_content(
233
+ menu_options: Dict[str, Dict[GraphSource, MenuOptions]],
234
+ has_maps: bool,
235
+ ) -> Dict[str, bool]:
236
+ options = next(iter(menu_options.values()))
237
+ content = {
238
+ "mass": GraphSource.CONTAINMENT_MASS in options,
239
+ "volume": GraphSource.CONTAINMENT_ACTUAL_VOLUME in options,
240
+ "unsmry": GraphSource.UNSMRY in options,
241
+ }
242
+ content["any_table"] = max(content.values())
243
+ content["maps"] = has_maps
244
+ content["zones"] = False
245
+ content["regions"] = False
246
+ content["plume_groups"] = False
247
+ if content["mass"] or content["volume"]:
248
+ content["zones"] = max(
249
+ len(inner_dict["zones"]) > 0
250
+ for outer_dict in menu_options.values()
251
+ for inner_dict in outer_dict.values()
252
+ )
253
+ content["regions"] = max(
254
+ len(inner_dict["regions"]) > 0
255
+ for outer_dict in menu_options.values()
256
+ for inner_dict in outer_dict.values()
257
+ )
258
+ content["plume_groups"] = max(
259
+ len(inner_dict["plume_groups"]) > 0
260
+ for outer_dict in menu_options.values()
261
+ for inner_dict in outer_dict.values()
262
+ )
263
+ return content
162
264
 
163
265
 
164
266
  def _process_file(file: Optional[str], ensemble_path: str) -> Optional[str]:
@@ -0,0 +1,60 @@
1
+ from typing import List, Optional
2
+
3
+ from webviz_subsurface._providers.ensemble_polygon_provider import (
4
+ EnsemblePolygonProviderFactory,
5
+ PolygonServer,
6
+ )
7
+ from webviz_subsurface._providers.ensemble_polygon_provider.ensemble_polygon_provider import (
8
+ PolygonsAddress,
9
+ )
10
+ from webviz_subsurface.plugins._co2_leakage._utilities.generic import BoundarySettings
11
+
12
+
13
+ class PolygonHandler:
14
+ def __init__(
15
+ self,
16
+ server: PolygonServer,
17
+ ensemble_path: str,
18
+ boundary_options: BoundarySettings,
19
+ ) -> None:
20
+ self._server = server
21
+ self._attribute = boundary_options["attribute"]
22
+ self._hazardous_name = boundary_options["hazardous_name"]
23
+ self._containment_name = boundary_options["containment_name"]
24
+ polygon_provider_factory = EnsemblePolygonProviderFactory.instance()
25
+ self._provider = polygon_provider_factory.create_from_ensemble_polygon_files(
26
+ ensemble_path,
27
+ boundary_options["polygon_file_pattern"],
28
+ )
29
+ server.add_provider(self._provider)
30
+
31
+ def extract_hazardous_poly_url(self, realization: List[int]) -> Optional[str]:
32
+ return self._extract_polygon_url(
33
+ self._hazardous_name, self._attribute, realization
34
+ )
35
+
36
+ def extract_containment_poly_url(self, realization: List[int]) -> Optional[str]:
37
+ return self._extract_polygon_url(
38
+ self._containment_name, self._attribute, realization
39
+ )
40
+
41
+ def _extract_polygon_url(
42
+ self,
43
+ name: str,
44
+ attribute: str,
45
+ realization: List[int],
46
+ ) -> Optional[str]:
47
+ if attribute is None:
48
+ return None
49
+ if len(realization) == 0:
50
+ return None
51
+ # NB! This always returns the url corresponding to the first realization
52
+ address = PolygonsAddress(
53
+ attribute=attribute,
54
+ name=name,
55
+ realization=realization[0],
56
+ )
57
+ return self._server.encode_partial_url(
58
+ provider_id=self._provider.provider_id(),
59
+ polygons_address=address,
60
+ )