webviz-subsurface 0.2.36__py3-none-any.whl → 0.2.37__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 (30) hide show
  1. webviz_subsurface/__init__.py +1 -1
  2. webviz_subsurface/_components/color_picker.py +1 -1
  3. webviz_subsurface/_providers/ensemble_polygon_provider/__init__.py +3 -0
  4. webviz_subsurface/_providers/ensemble_polygon_provider/_polygon_discovery.py +97 -0
  5. webviz_subsurface/_providers/ensemble_polygon_provider/_provider_impl_file.py +226 -0
  6. webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider.py +53 -0
  7. webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider_factory.py +99 -0
  8. webviz_subsurface/_providers/ensemble_polygon_provider/polygon_server.py +125 -0
  9. webviz_subsurface/plugins/_co2_leakage/_plugin.py +531 -377
  10. webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py +9 -0
  11. webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +169 -173
  12. webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +329 -84
  13. webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py +147 -0
  14. webviz_subsurface/plugins/_co2_leakage/_utilities/ensemble_well_picks.py +105 -0
  15. webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +170 -2
  16. webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +189 -96
  17. webviz_subsurface/plugins/_co2_leakage/_utilities/polygon_handler.py +60 -0
  18. webviz_subsurface/plugins/_co2_leakage/_utilities/summary_graphs.py +77 -173
  19. webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +29 -21
  20. webviz_subsurface/plugins/_co2_leakage/_utilities/unsmry_data_provider.py +108 -0
  21. webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +30 -18
  22. webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +805 -343
  23. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/METADATA +2 -2
  24. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/RECORD +30 -19
  25. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/WHEEL +1 -1
  26. /webviz_subsurface/plugins/_co2_leakage/_utilities/{fault_polygons.py → fault_polygons_handler.py} +0 -0
  27. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/LICENSE +0 -0
  28. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/LICENSE.chromedriver +0 -0
  29. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.dist-info}/entry_points.txt +0 -0
  30. {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.37.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,80 @@ 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,
33
+ )
34
+ from webviz_subsurface.plugins._co2_leakage._utilities.polygon_handler import (
35
+ PolygonHandler,
23
36
  )
24
- from webviz_subsurface.plugins._map_viewer_fmu._tmp_well_pick_provider import (
25
- WellPickProvider,
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]:
78
+ webviz_settings: WebvizSettings,
79
+ ensembles: List[str],
80
+ mapping: Optional[Dict[str, str]],
81
+ ) -> FilteredMapAttribute:
35
82
  if mapping is None:
36
83
  # 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()}
84
+ mapping = build_mapping(webviz_settings, ensembles)
85
+ final_attributes = {
86
+ (MapAttribute[key].value if key in MapAttribute.__members__ else key): value
87
+ for key, value in mapping.items()
88
+ }
89
+ return FilteredMapAttribute(final_attributes)
47
90
 
48
91
 
49
92
  def init_surface_providers(
@@ -60,105 +103,155 @@ def init_surface_providers(
60
103
 
61
104
 
62
105
  def init_well_pick_provider(
63
- well_pick_dict: Dict[str, Optional[str]],
106
+ ensemble_paths: Dict[str, str],
107
+ well_pick_path: Optional[str],
64
108
  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
80
-
81
-
82
- def init_table_provider(
109
+ ) -> Dict[str, EnsembleWellPicks]:
110
+ if well_pick_path is None:
111
+ return {}
112
+
113
+ return {
114
+ ens: EnsembleWellPicks(
115
+ ens_p, well_pick_path, map_surface_names_to_well_pick_names
116
+ )
117
+ for ens, ens_p in ensemble_paths.items()
118
+ }
119
+
120
+
121
+ def init_polygon_provider_handlers(
122
+ server: PolygonServer,
123
+ ensemble_paths: Dict[str, str],
124
+ options: Optional[BoundarySettings],
125
+ ) -> Dict[str, PolygonHandler]:
126
+ filled_options: BoundarySettings = {
127
+ "polygon_file_pattern": "share/results/polygon/*.csv",
128
+ "attribute": "boundary",
129
+ "hazardous_name": "hazardous",
130
+ "containment_name": "containment",
131
+ }
132
+ if options is not None:
133
+ filled_options.update(options)
134
+ return {
135
+ ens: PolygonHandler(
136
+ server,
137
+ ens_path,
138
+ filled_options,
139
+ )
140
+ for ens, ens_path in ensemble_paths.items()
141
+ }
142
+
143
+
144
+ def init_unsmry_data_providers(
145
+ ensemble_roots: Dict[str, str],
146
+ table_rel_path: Optional[str],
147
+ ) -> Dict[str, UnsmryDataProvider]:
148
+ if table_rel_path is None:
149
+ return {}
150
+ factory = EnsembleTableProviderFactory.instance()
151
+ providers = {
152
+ ens: _init_ensemble_table_provider(factory, ens, ens_path, table_rel_path)
153
+ for ens, ens_path in ensemble_roots.items()
154
+ }
155
+ return {k: UnsmryDataProvider(v) for k, v in providers.items() if v is not None}
156
+
157
+
158
+ def init_containment_data_providers(
83
159
  ensemble_roots: Dict[str, str],
84
160
  table_rel_path: str,
85
- ) -> Dict[str, EnsembleTableProvider]:
86
- providers = {}
161
+ ) -> Dict[str, ContainmentDataProvider]:
87
162
  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)
163
+ providers = {
164
+ ens: _init_ensemble_table_provider(factory, ens, ens_path, table_rel_path)
165
+ for ens, ens_path in ensemble_roots.items()
166
+ }
167
+ return {
168
+ k: ContainmentDataProvider(v) for k, v in providers.items() if v is not None
169
+ }
98
170
 
171
+
172
+ def _init_ensemble_table_provider(
173
+ factory: EnsembleTableProviderFactory,
174
+ ens: str,
175
+ ens_path: str,
176
+ table_rel_path: str,
177
+ ) -> Optional[EnsembleTableProvider]:
178
+ try:
179
+ return factory.create_from_per_realization_arrow_file(ens_path, table_rel_path)
180
+ except (KeyError, ValueError) as exc:
99
181
  try:
100
- providers[ens] = factory.create_from_per_realization_csv_file(
182
+ return factory.create_from_per_realization_csv_file(
101
183
  ens_path, table_rel_path
102
184
  )
103
- except (KeyError, ValueError) as exc:
185
+ except (KeyError, ValueError) as exc2:
104
186
  LOGGER.warning(
105
- f'Did not load "{table_rel_path}" for ensemble "{ens}" with error {exc}'
187
+ f'\nTried reading "{table_rel_path}" for ensemble "{ens}" as csv with'
188
+ f" error \n- {exc2}, \nand as arrow with error \n- {exc}"
106
189
  )
107
- return providers
190
+ return None
108
191
 
109
192
 
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
193
+ def init_realizations(ensemble_paths: Dict[str, str]) -> Dict[str, List[int]]:
194
+ realization_per_ens = {}
195
+ for ens, ens_path in ensemble_paths.items():
196
+ scratch_ensemble = ScratchEnsemble("dummyEnsembleName", paths=ens_path).filter(
197
+ "OK"
198
+ )
199
+ realization_per_ens[ens] = sorted(list(scratch_ensemble.realizations.keys()))
200
+ return realization_per_ens
120
201
 
121
202
 
122
203
  def init_menu_options(
123
204
  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]]]] = {}
205
+ mass_table: Dict[str, ContainmentDataProvider],
206
+ actual_volume_table: Dict[str, ContainmentDataProvider],
207
+ unsmry_providers: Dict[str, UnsmryDataProvider],
208
+ ) -> Dict[str, Dict[GraphSource, MenuOptions]]:
209
+ options: Dict[str, Dict[GraphSource, MenuOptions]] = {}
130
210
  for ens in ensemble_roots.keys():
131
211
  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
- }
212
+ if ens in mass_table:
213
+ options[ens][GraphSource.CONTAINMENT_MASS] = mass_table[ens].menu_options
214
+ if ens in actual_volume_table:
215
+ options[ens][GraphSource.CONTAINMENT_ACTUAL_VOLUME] = actual_volume_table[
216
+ ens
217
+ ].menu_options
218
+ if ens in unsmry_providers:
219
+ options[ens][GraphSource.UNSMRY] = unsmry_providers[ens].menu_options
144
220
  return options
145
221
 
146
222
 
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
- ]
223
+ def init_dictionary_of_content(
224
+ menu_options: Dict[str, Dict[GraphSource, MenuOptions]],
225
+ has_maps: bool,
226
+ ) -> Dict[str, bool]:
227
+ options = next(iter(menu_options.values()))
228
+ content = {
229
+ "mass": GraphSource.CONTAINMENT_MASS in options,
230
+ "volume": GraphSource.CONTAINMENT_ACTUAL_VOLUME in options,
231
+ "unsmry": GraphSource.UNSMRY in options,
232
+ }
233
+ content["any_table"] = max(content.values())
234
+ content["maps"] = has_maps
235
+ content["zones"] = False
236
+ content["regions"] = False
237
+ content["plume_groups"] = False
238
+ if content["mass"] or content["volume"]:
239
+ content["zones"] = max(
240
+ len(inner_dict["zones"]) > 0
241
+ for outer_dict in menu_options.values()
242
+ for inner_dict in outer_dict.values()
243
+ )
244
+ content["regions"] = max(
245
+ len(inner_dict["regions"]) > 0
246
+ for outer_dict in menu_options.values()
247
+ for inner_dict in outer_dict.values()
248
+ )
249
+ content["plume_groups"] = max(
250
+ len(inner_dict["plume_groups"]) > 0
251
+ for outer_dict in menu_options.values()
252
+ for inner_dict in outer_dict.values()
253
+ )
254
+ return content
162
255
 
163
256
 
164
257
  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
+ )