├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── subsurface.yml ├── .gitignore ├── .isort.cfg ├── .pylintrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── bandit.yml ├── deploy_docker_image.sh ├── mypy.ini ├── pytest.ini ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── data │ ├── colormap.txt │ ├── gruptree.csv │ ├── parameters.csv │ ├── realizations.csv │ ├── surface_png.txt │ ├── surface_zarr.np.gz │ ├── vfp.arrow │ └── volumes.csv ├── integration_tests │ ├── __init__.py │ ├── test_parameter_filter.py │ └── test_surface_selector.py └── unit_tests │ ├── __init__.py │ ├── abbreviations_tests │ ├── __init__.py │ └── test_reservoir_simulation.py │ ├── data_input │ ├── __init__.py │ ├── test_calc_from_cumulatives.py │ └── test_image_processing.py │ ├── mocks │ ├── __init__.py │ └── ensemble_summary_provider_dummy.py │ ├── model_tests │ ├── __init__.py │ ├── test_ensemble_model.py │ ├── test_ensemble_set_model.py │ ├── test_gruptree_model.py │ ├── test_property_statistics_model.py │ ├── test_surface_set_model.py │ ├── test_well_attributes_model.py │ └── test_well_set_model.py │ ├── plugin_tests │ ├── __init__.py │ ├── test_grouptree.py │ ├── test_simulation_time_series │ │ ├── __init__.py │ │ ├── mocks │ │ │ ├── __init__.py │ │ │ └── derived_vectors_accessor_ensemble_summary_provider_mock.py │ │ └── test_utils │ │ │ ├── __init__.py │ │ │ ├── test_create_vector_traces_utils.py │ │ │ ├── test_dataframe_utils.py │ │ │ ├── test_datetime_utils.py │ │ │ ├── test_delta_ensemble_utils.py │ │ │ ├── test_derived_delta_ensemble_vectors_accessor_impl.py │ │ │ ├── test_derived_ensemble_vectors_accessor_impl.py │ │ │ ├── test_derived_ensemble_vectors_accessor_utils.py │ │ │ ├── test_derived_vector_accessor.py │ │ │ ├── test_ensemble_summary_provider_set_utils.py │ │ │ ├── test_from_timeseries_cumulatives.py │ │ │ ├── test_history_vectors.py │ │ │ ├── test_trace_line_shape.py │ │ │ └── test_vector_statistics.py │ ├── test_tornado_data.py │ └── test_well_completions.py │ ├── provider_tests │ ├── __init__.py │ ├── test_ensemble_summary_provider.py │ ├── test_ensemble_summary_provider_impl_arrow_lazy.py │ ├── test_ensemble_summary_provider_impl_arrow_presampled.py │ ├── test_ensemble_summary_provider_resampling.py │ └── test_ensemble_table_provider.py │ └── utils_tests │ ├── __init__.py │ ├── test_dataframe_utils.py │ ├── test_ensemble_summary_provider_set │ ├── __init__.py │ ├── mocks │ │ └── ensemble_summary_provider_mock.py │ └── test_ensemble_summary_provider_set.py │ ├── test_formatting.py │ └── test_simulation_timeseries.py └── webviz_subsurface ├── __init__.py ├── _abbreviations ├── __init__.py ├── abbreviation_data │ ├── reservoir_simulation_unit_terminology.json │ ├── si_prefixes.json │ └── volume_terminology.json ├── number_formatting.py ├── reservoir_simulation.py └── volume_terminology.py ├── _assets ├── css │ ├── block_options.css │ ├── container.css │ ├── inplace_volumes.css │ ├── modal.css │ └── structural_uncertainty.css └── js │ └── clientside_functions.js ├── _components ├── __init__.py ├── color_picker.py ├── parameter_filter.py └── tornado │ ├── __init__.py │ ├── _tornado_bar_chart.py │ ├── _tornado_data.py │ ├── _tornado_table.py │ └── tornado_widget.py ├── _datainput ├── __init__.py ├── eclipse_init_io │ ├── __init__.py │ ├── pvt_common.py │ ├── pvt_gas.py │ ├── pvt_oil.py │ └── pvt_water.py ├── eclipse_unit.py ├── fmu_input.py ├── from_timeseries_cumulatives.py ├── grid.py ├── history_match.py ├── image_processing.py ├── inplace_volumes.py ├── pvt_data.py ├── relative_permeability.py ├── seismic.py ├── surface.py ├── units.py ├── well.py ├── well_completions.py └── xsection.py ├── _figures ├── __init__.py ├── barchart.py ├── px_figure.py ├── scatterplot.py └── timeseries_figure.py ├── _models ├── __init__.py ├── caching_ensemble_set_model_factory.py ├── ensemble_model.py ├── ensemble_set_model.py ├── gruptree_model.py ├── inplace_volumes_model.py ├── observation_model.py ├── parameter_model.py ├── stratigraphy_model.py ├── surface_leaflet_model.py ├── surface_set_model.py ├── well_attributes_model.py └── well_set_model.py ├── _private_plugins ├── __init__.py └── surface_selector.py ├── _providers ├── __init__.py ├── ensemble_fault_polygons_provider │ ├── __init__.py │ ├── _fault_polygons_discovery.py │ ├── _provider_impl_file.py │ ├── ensemble_fault_polygons_provider.py │ ├── ensemble_fault_polygons_provider_factory.py │ └── fault_polygons_server.py ├── ensemble_grid_provider │ ├── __init__.py │ ├── _egrid_file_discovery.py │ ├── _roff_file_discovery.py │ ├── _xtgeo_to_vtk_explicit_structured_grid.py │ ├── ensemble_grid_provider.py │ ├── ensemble_grid_provider_factory.py │ ├── grid_viz_service.py │ ├── provider_impl_egrid.py │ └── provider_impl_roff.py ├── ensemble_polygon_provider │ ├── __init__.py │ ├── _polygon_discovery.py │ ├── _provider_impl_file.py │ ├── ensemble_polygon_provider.py │ ├── ensemble_polygon_provider_factory.py │ └── polygon_server.py ├── ensemble_summary_provider │ ├── __init__.py │ ├── _arrow_unsmry_import.py │ ├── _csv_import.py │ ├── _dataframe_utils.py │ ├── _field_metadata.py │ ├── _provider_impl_arrow_lazy.py │ ├── _provider_impl_arrow_presampled.py │ ├── _resampling.py │ ├── _table_utils.py │ ├── dev_compare_fmu_to_lazy_provider.py │ ├── dev_provider_perf_testing.py │ ├── ensemble_summary_provider.py │ ├── ensemble_summary_provider_factory.py │ └── utils.py ├── ensemble_surface_provider │ ├── __init__.py │ ├── _provider_impl_file.py │ ├── _stat_surf_cache.py │ ├── _surface_discovery.py │ ├── _surface_to_float32_array.py │ ├── _surface_to_image.py │ ├── _types.py │ ├── dev_experiments.py │ ├── dev_surface_server_lazy.py │ ├── ensemble_surface_provider.py │ ├── ensemble_surface_provider_factory.py │ ├── surface_array_server.py │ └── surface_image_server.py ├── ensemble_table_provider │ ├── __init__.py │ ├── _field_metadata.py │ ├── _table_import.py │ ├── ensemble_table_provider.py │ ├── ensemble_table_provider_factory.py │ └── ensemble_table_provider_impl_arrow.py └── well_provider │ ├── __init__.py │ ├── _provider_impl_file.py │ ├── dev_experiments.py │ ├── well_provider.py │ ├── well_provider_factory.py │ └── well_server.py ├── _utils ├── __init__.py ├── colors.py ├── dataframe_utils.py ├── design_matrix.py ├── ensemble_summary_provider_set.py ├── ensemble_summary_provider_set_factory.py ├── ensemble_table_provider_set.py ├── ensemble_table_provider_set_factory.py ├── enum_shim.py ├── fanchart_plotting.py ├── formatting.py ├── parameter_response.py ├── perf_timer.py ├── simulation_timeseries.py ├── statistics_plotting.py ├── unique_theming.py ├── user_defined_vector_definitions.py ├── vector_calculator.py ├── vector_selector.py └── webvizstore_functions.py ├── plugins ├── __init__.py ├── _assisted_history_matching_analysis.py ├── _bhp_qc │ ├── __init__.py │ ├── _plugin.py │ ├── _plugin_ids.py │ ├── shared_settings │ │ ├── __init__.py │ │ └── _filter.py │ ├── view_elements │ │ ├── __init__.py │ │ └── _graph.py │ └── views │ │ ├── __init__.py │ │ ├── _bar_chart.py │ │ ├── _fan_chart.py │ │ ├── _line_chart.py │ │ └── _view_functions.py ├── _co2_leakage │ ├── __init__.py │ ├── _error.py │ ├── _plugin.py │ ├── _types.py │ ├── _utilities │ │ ├── __init__.py │ │ ├── _misc.py │ │ ├── callbacks.py │ │ ├── co2volume.py │ │ ├── color_tables.py │ │ ├── containment_data_provider.py │ │ ├── containment_info.py │ │ ├── ensemble_well_picks.py │ │ ├── fault_polygons_handler.py │ │ ├── generic.py │ │ ├── initialization.py │ │ ├── plume_extent.py │ │ ├── polygon_handler.py │ │ ├── summary_graphs.py │ │ ├── surface_publishing.py │ │ └── unsmry_data_provider.py │ └── views │ │ ├── __init__.py │ │ └── mainview │ │ ├── __init__.py │ │ ├── mainview.py │ │ └── settings.py ├── _disk_usage.py ├── _grid_viewer_fmu │ ├── __init__.py │ ├── _layout_elements.py │ ├── _plugin.py │ ├── _routes.py │ ├── _types.py │ └── views │ │ ├── __init__.py │ │ └── view_3d │ │ ├── __init__.py │ │ ├── _view_3d.py │ │ ├── settings │ │ ├── __init__.py │ │ ├── _color_scale.py │ │ ├── _data_selection.py │ │ └── _grid_filter.py │ │ └── view_elements │ │ ├── __init__.py │ │ └── _vtk_view_3d_element.py ├── _group_tree │ ├── __init__.py │ ├── _plugin.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ └── _ensemble_group_tree_data.py │ └── _views │ │ ├── __init__.py │ │ └── _group_tree_view │ │ ├── __init__.py │ │ ├── _view.py │ │ └── _view_element.py ├── _history_match.py ├── _horizon_uncertainty_viewer │ ├── __init__.py │ ├── _huv_table.py │ ├── _huv_xsection.py │ └── horizon_uncertainty_viewer.py ├── _inplace_volumes.py ├── _inplace_volumes_onebyone.py ├── _line_plotter_fmu │ ├── __init__.py │ ├── controllers │ │ ├── __init__.py │ │ ├── build_figure.py │ │ └── update_figure_clientside.py │ ├── figures │ │ ├── __init__.py │ │ └── plotly_line_plot.py │ ├── line_plotter_fmu.py │ └── views │ │ ├── __init__.py │ │ ├── data_selectors_view.py │ │ ├── plot_options_view.py │ │ └── plot_traces_view.py ├── _map_viewer_fmu │ ├── __init__.py │ ├── _layer_model.py │ ├── _tmp_well_pick_provider.py │ ├── _types.py │ ├── _utils.py │ ├── callbacks.py │ ├── color_tables.py │ ├── layout.py │ └── map_viewer_fmu.py ├── _morris_plot.py ├── _parameter_analysis │ ├── __init__.py │ ├── _plugin.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ ├── _datetime_utils.py │ │ ├── _parameters_model.py │ │ └── _provider_timesseries_datamodel.py │ └── _views │ │ ├── __init__.py │ │ ├── _parameter_distributions_view │ │ ├── __init__.py │ │ ├── _settings │ │ │ ├── __init__.py │ │ │ ├── _ensembles.py │ │ │ ├── _parameters.py │ │ │ └── _visualization_type.py │ │ ├── _view.py │ │ └── _view_element.py │ │ └── _parameter_response_view │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _options.py │ │ ├── _parameter_filter.py │ │ ├── _selections.py │ │ └── _vizualisation.py │ │ ├── _utils │ │ ├── __init__.py │ │ └── _color_figure.py │ │ ├── _view.py │ │ └── _view_element.py ├── _parameter_correlation │ ├── __init__.py │ ├── _error.py │ ├── _plugin.py │ ├── _plugin_ids.py │ └── views │ │ ├── __init__.py │ │ └── parameter_plot │ │ ├── __init__.py │ │ ├── _parameter_plot.py │ │ └── settings │ │ ├── __init__.py │ │ └── _parameter_settings.py ├── _parameter_distribution.py ├── _parameter_parallel_coordinates.py ├── _parameter_response_correlation.py ├── _prod_misfit │ ├── __init__.py │ ├── _plugin.py │ ├── _plugin_ids.py │ ├── shared_settings │ │ ├── __init__.py │ │ └── _filter.py │ ├── utils │ │ ├── __init__.py │ │ ├── make_dataframes.py │ │ └── make_figures.py │ ├── view_elements │ │ ├── __init__.py │ │ └── _graph.py │ └── views │ │ ├── __init__.py │ │ ├── _production_misfit_per_real.py │ │ ├── _view_functions.py │ │ ├── _well_production_coverage.py │ │ └── _well_production_heatmap.py ├── _property_statistics │ ├── __init__.py │ ├── controllers │ │ ├── __init__.py │ │ ├── property_delta_controller.py │ │ ├── property_qc_controller.py │ │ └── property_response_controller.py │ ├── data_loaders │ │ └── __init__.py │ ├── figures │ │ ├── __init__.py │ │ └── correlation_figure.py │ ├── models │ │ ├── __init__.py │ │ ├── ensemble_timeseries_datamodel.py │ │ ├── property_statistics_model.py │ │ └── simulation_timeseries_model.py │ ├── property_statistics.py │ ├── utils │ │ ├── __init__.py │ │ ├── colors.py │ │ └── surface.py │ └── views │ │ ├── __init__.py │ │ ├── main_view.py │ │ ├── property_delta_view.py │ │ ├── property_qc_view.py │ │ ├── property_response_view.py │ │ └── selector_view.py ├── _pvt_plot │ ├── __init__.py │ ├── _plugin.py │ └── _views │ │ ├── __init__.py │ │ └── _pvt │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _data_settings.py │ │ └── _view_settings.py │ │ ├── _utils │ │ ├── __init__.py │ │ └── _plot_utils.py │ │ └── _view.py ├── _relative_permeability.py ├── _reservoir_simulation_timeseries.py ├── _reservoir_simulation_timeseries_onebyone.py ├── _reservoir_simulation_timeseries_regional.py ├── _rft_plotter │ ├── __init__.py │ ├── _plugin.py │ ├── _reusable_settings.py │ ├── _reusable_view_element.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ ├── _formation_figure.py │ │ └── _rft_plotter_data_model.py │ └── _views │ │ ├── __init__.py │ │ ├── _map_view │ │ ├── __init__.py │ │ ├── _settings │ │ │ ├── __init__.py │ │ │ ├── _formation_plot_settings.py │ │ │ └── _map_settings.py │ │ ├── _utils │ │ │ ├── __init__.py │ │ │ └── _map_figure.py │ │ └── _view.py │ │ ├── _misfit_per_real_view │ │ ├── __init__.py │ │ ├── _settings.py │ │ ├── _utils │ │ │ ├── __init__.py │ │ │ └── _misfit_per_real_figure.py │ │ └── _view.py │ │ ├── _parameter_response_view │ │ ├── __init__.py │ │ ├── _settings │ │ │ ├── __init__.py │ │ │ ├── _options.py │ │ │ ├── _parameter_filter.py │ │ │ └── _selections.py │ │ └── _view.py │ │ ├── _qc_view │ │ ├── __init__.py │ │ ├── _settings.py │ │ ├── _table_view_element.py │ │ └── _view.py │ │ └── _sim_vs_obs_view │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _ensembles.py │ │ ├── _plot_type.py │ │ └── _size_color_settings.py │ │ ├── _utils │ │ ├── __init__.py │ │ ├── _crossplot_figure.py │ │ └── _errorplot_figure.py │ │ └── _view.py ├── _running_time_analysis_fmu.py ├── _segy_viewer.py ├── _seismic_misfit.py ├── _simulation_time_series │ ├── __init__.py │ ├── _plugin.py │ └── _views │ │ ├── __init__.py │ │ └── _subplot_view │ │ ├── __init__.py │ │ ├── _property_serialization │ │ ├── __init__.py │ │ ├── ensemble_subplot_builder.py │ │ ├── graph_figure_builder_base.py │ │ └── vector_subplot_builder.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _ensembles.py │ │ ├── _filter_realization.py │ │ ├── _group_by.py │ │ ├── _resampling_frequency.py │ │ ├── _time_series.py │ │ └── _visualization.py │ │ ├── _types │ │ ├── __init__.py │ │ └── types.py │ │ ├── _utils │ │ ├── __init__.py │ │ ├── create_vector_traces_utils.py │ │ ├── dataframe_utils.py │ │ ├── datetime_utils.py │ │ ├── delta_ensemble_utils.py │ │ ├── derived_ensemble_vectors_accessor_utils.py │ │ ├── derived_vectors_accessor │ │ │ ├── __init__.py │ │ │ ├── derived_delta_ensemble_vectors_accessor_impl.py │ │ │ ├── derived_ensemble_vectors_accessor_impl.py │ │ │ └── derived_vectors_accessor.py │ │ ├── ensemble_summary_provider_set_utils.py │ │ ├── from_timeseries_cumulatives.py │ │ ├── history_vectors.py │ │ ├── trace_line_shape.py │ │ └── vector_statistics.py │ │ ├── _view.py │ │ └── _view_elements │ │ ├── __init__.py │ │ └── _subplot_graph.py ├── _simulation_time_series_onebyone │ ├── __init__.py │ ├── _plugin.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ ├── _datetime_utils.py │ │ ├── _onebyone_timeseries_figure.py │ │ └── _simulation_time_series_onebyone_datamodel.py │ └── _views │ │ ├── __init__.py │ │ └── _onebyone_view │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _general_settings.py │ │ ├── _selections.py │ │ ├── _sensitivity_filter.py │ │ └── _vizualisation.py │ │ ├── _view.py │ │ └── _view_elements │ │ ├── __init__.py │ │ ├── _bottom_visualization_view_element.py │ │ └── _general_view_element.py ├── _structural_uncertainty │ ├── __init__.py │ ├── _tour_steps.py │ ├── controllers │ │ ├── __init__.py │ │ ├── dialog_controller.py │ │ ├── intersection_controller.py │ │ ├── intersection_source_controller.py │ │ ├── map_controller.py │ │ ├── realization_filter_controller.py │ │ └── uncertainty_table_controller.py │ ├── figures │ │ ├── __init__.py │ │ └── intersection.py │ ├── structural_uncertainty.py │ └── views │ │ ├── __init__.py │ │ ├── clientside_stores.py │ │ ├── dialog.py │ │ ├── intersection_and_map.py │ │ ├── intersection_data.py │ │ ├── map_data.py │ │ ├── realization_filter.py │ │ └── uncertainty_table.py ├── _subsurface_map.py ├── _surface_viewer_fmu.py ├── _surface_with_grid_cross_section.py ├── _surface_with_seismic_cross_section.py ├── _swatinit_qc │ ├── __init__.py │ ├── _business_logic.py │ ├── _callbacks.py │ ├── _figures.py │ ├── _layout.py │ ├── _markdown.py │ └── _plugin.py ├── _tornado_plotter_fmu │ ├── __init__.py │ ├── _error.py │ ├── _plugin.py │ ├── shared_settings │ │ ├── __init__.py │ │ ├── _filters.py │ │ ├── _selectors.py │ │ └── _view_settings.py │ └── views │ │ ├── __init__.py │ │ ├── plot_view │ │ ├── __init__.py │ │ ├── _plot_view.py │ │ └── view_elements │ │ │ ├── __init__.py │ │ │ └── plot.py │ │ └── table_view │ │ ├── __init__.py │ │ ├── _table_view.py │ │ └── view_elements │ │ ├── __init__.py │ │ └── table.py ├── _vfp_analysis │ ├── __init__.py │ ├── _plugin.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ └── _vfp_data_model.py │ └── _views │ │ ├── __init__.py │ │ └── _vfp_view │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _filters.py │ │ ├── _pressure_option.py │ │ ├── _selections.py │ │ └── _vizualisation.py │ │ ├── _utils │ │ ├── __init__.py │ │ └── _vfp_figure_builder.py │ │ ├── _view.py │ │ └── _view_elements │ │ ├── __init__.py │ │ └── _vfp_graph.py ├── _volumetric_analysis │ ├── __init__.py │ ├── controllers │ │ ├── __init__.py │ │ ├── comparison_controllers.py │ │ ├── distribution_controllers.py │ │ ├── export_data_controllers.py │ │ ├── fipfile_qc_controller.py │ │ ├── layout_controllers.py │ │ ├── selections_controllers.py │ │ └── tornado_controllers.py │ ├── utils │ │ ├── __init__.py │ │ ├── table_and_figure_utils.py │ │ └── utils.py │ ├── views │ │ ├── __init__.py │ │ ├── clientside_stores.py │ │ ├── comparison_layout.py │ │ ├── distribution_main_layout.py │ │ ├── filter_view.py │ │ ├── fipfile_qc_layout.py │ │ ├── main_view.py │ │ ├── selections_view.py │ │ └── tornado_view.py │ ├── volume_validator_and_combinator.py │ └── volumetric_analysis.py ├── _well_analysis │ ├── __init__.py │ ├── _plugin.py │ ├── _types.py │ ├── _utils │ │ ├── __init__.py │ │ └── _ensemble_well_analysis_data.py │ └── _views │ │ ├── __init__.py │ │ ├── _well_control_view │ │ ├── __init__.py │ │ ├── _utils │ │ │ ├── __init__.py │ │ │ └── _well_control_figure.py │ │ ├── _view.py │ │ └── _view_element.py │ │ └── _well_overview_view │ │ ├── __init__.py │ │ ├── _settings │ │ ├── __init__.py │ │ ├── _chart_type.py │ │ ├── _filters.py │ │ ├── _layout_options.py │ │ ├── _selections.py │ │ └── _statistical_options.py │ │ ├── _utils │ │ ├── __init__.py │ │ └── _well_overview_figure.py │ │ ├── _view.py │ │ └── _view_element.py ├── _well_completion │ ├── __init__.py │ ├── _plugin.py │ ├── _utils │ │ ├── __init__.py │ │ └── _well_completion_data_model.py │ └── _views │ │ ├── __init__.py │ │ └── _well_completion_view │ │ ├── __init__.py │ │ ├── _view.py │ │ └── _view_element.py ├── _well_completions │ ├── __init__.py │ ├── _business_logic.py │ ├── _callbacks.py │ ├── _layout.py │ └── _plugin.py ├── _well_cross_section.py ├── _well_cross_section_fmu.py └── _well_log_viewer │ ├── __init__.py │ ├── _validate_log_templates.py │ ├── controllers │ ├── __init__.py │ └── _well_controller.py │ ├── utils │ ├── __init__.py │ ├── default_color_tables.py │ └── xtgeo_well_log_to_json.py │ └── well_log_viewer.py └── smry2arrow_batch.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Remember that this is an open source project. Don't include e.g. screenshots with data that can't be publicly available._ 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **How to reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | 3 | #contact_links: 4 | # - name: GitHub Community Support 5 | # url: https://github.community/ 6 | # about: Please ask and answer questions here. 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Remember that this is an open source project. Don't include e.g. screenshots with data that can't be publicly available._ 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | *Insert a description of your pull request (PR) here, and check off the boxes below when they are done.* 2 | 3 | --- 4 | 5 | ### Contributor checklist 6 | 7 | - [ ] :tada: This PR closes #ISSUE_NUMBER. 8 | - [ ] :scroll: I have broken down my PR into the following tasks: 9 | - [ ] Task 1 10 | - [ ] Task 2 11 | - [ ] :robot: I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR. 12 | - [ ] :book: I have considered adding a new entry in `CHANGELOG.md`, and added it if should be communicated there. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eggs 2 | *.egg-info 3 | __pycache__ 4 | node_modules 5 | venv 6 | .vscode 7 | .pytest_cache 8 | *.pyc 9 | .DS_Store 10 | dist 11 | *~ 12 | *.mypy_cache* 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile=black 3 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # As a temporary workaround for https://github.com/PyCQA/pylint/issues/4577 4 | init-hook = "import astroid; astroid.context.InferenceContext.max_inferred = 500" 5 | 6 | [MESSAGES CONTROL] 7 | 8 | disable = missing-docstring, duplicate-code, logging-fstring-interpolation, unspecified-encoding 9 | enable = useless-suppression 10 | 11 | [DESIGN] 12 | 13 | max-args = 8 14 | max-branches = 14 15 | max-attributes = 15 16 | min-public-methods = 1 17 | 18 | [BASIC] 19 | 20 | # Variable names which should always be accepted 21 | good-names = i, 22 | j, 23 | k, 24 | x, 25 | y, 26 | z, 27 | df, 28 | _ 29 | 30 | 31 | [MISCELLANEOUS] 32 | 33 | # List of note tags to take in consideration, separated by a comma. 34 | notes=FIXME 35 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you discover a security vulnerability in this project, please follow these steps to responsibly disclose it: 2 | 3 | 1. **Do not** create a public GitHub issue for the vulnerability. 4 | 5 | 2. Follow our guideline for Responsible Disclosure Policy at https://www.equinor.com/about-us/csirt to report the issue 6 | 7 | The following information will help us triage your report more quickly: 8 | 9 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 10 | - Full paths of source file(s) related to the manifestation of the issue 11 | - The location of the affected source code (tag/branch/commit or direct URL) 12 | - Any special configuration required to reproduce the issue 13 | - Step-by-step instructions to reproduce the issue 14 | - Proof-of-concept or exploit code (if possible) 15 | - Impact of the issue, including how an attacker might exploit the issue 16 | - We prefer all communications to be in English. 17 | -------------------------------------------------------------------------------- /bandit.yml: -------------------------------------------------------------------------------- 1 | #Allow use of assert for tests 2 | skips: ['B101'] 3 | -------------------------------------------------------------------------------- /deploy_docker_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 6 | 7 | docker push webviz/example_subsurface_image:equinor-theme 8 | 9 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | # Global options 2 | 3 | [mypy] 4 | ignore_missing_imports = True 5 | disallow_untyped_defs = True 6 | show_error_codes = True 7 | 8 | # Temporarily allow implicit optional until pydantic handles JSON schema generation. 9 | # mypy >= 0.990 has changed its default to no_implicit_optional=True. 10 | # When removed - utilize the following make the code base implicit optional 11 | # type hints PEP 484 compliant: 12 | # https://github.com/hauntsaninja/no_implicit_optional 13 | implicit_optional = True 14 | 15 | # TODO(Sigurd) 16 | # Temporarily disable mypy for the following modules until incorpoartion 17 | # of type hints have been completed. 18 | # This is not intended to be a permanent solution but rather a temporary fix 19 | # that will allow us to start utilizing mypy in CI. 20 | # If there are whole files that should be permanently exempted from 21 | # mypy's type checking you should use a '# mypy: ignore-errors' tag at the 22 | # top of the file instead. 23 | 24 | [mypy-webviz_subsurface.plugins._assisted_history_matching_analysis.*] 25 | ignore_errors=True 26 | 27 | [mypy-webviz_subsurface.plugins._horizon_uncertainty_viewer.*] 28 | ignore_errors=True 29 | 30 | [mypy-webviz_subsurface.plugins._parameter_parallel_coordinates.*] 31 | ignore_errors=True 32 | 33 | [mypy-webviz_subsurface.plugins._parameter_response_correlation.*] 34 | ignore_errors=True 35 | 36 | [mypy-webviz_subsurface.plugins._relative_permeability.*] 37 | ignore_errors=True 38 | 39 | [mypy-webviz_subsurface.plugins._surface_with_grid_cross_section.*] 40 | ignore_errors=True 41 | 42 | [mypy-webviz_subsurface.plugins._surface_with_seismic_cross_section.*] 43 | ignore_errors=True 44 | 45 | [mypy-webviz_subsurface.plugins._inplace_volumes.*] 46 | ignore_errors=True 47 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests/ 3 | webdriver = Chrome 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/colormap.txt: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAABCAYAAAAxWXB3AAAAuElEQVR4nI2NyxUDIQwDR6K0lJD+W1nnABgvIZ8DT7JGNnroieRAQjJYMFQ2SDBUk0mrl16odGce05de9Z2zzStLLhEuvurIZzeZOedizd7mT70f7JOe7v7XA/jBBaH4ztn3462z37l1c7/ys1f6QFNZuUZ+1+JZ3oVN79FxctLvLB/XIQuslbe3+eSv7LVyd/KmC9O13Vjf63zt7r3kW7dR/iVuvv/H8NBE1/SiIayhiCZjhDFN5gX8UYgJzVykqAAAAABJRU5ErkJggg== -------------------------------------------------------------------------------- /tests/data/gruptree.csv: -------------------------------------------------------------------------------- 1 | DATE,CHILD,KEYWORD,PARENT,VFP_TABLE,ALQ,ALQ_SURFACE_DENSITY,TERMINAL_PRESSURE,AS_CHOKE,ADD_GAS_LIFT_GAS,CHOKE_GROUP,SOURCE_SINK_GROUP,NETWORK_VALUE_TYPE 2 | 2023-01-01,FIELD,GRUPTREE,,,,,,,,,, 3 | 2023-01-01,NODE,GRUPTREE,FIELD,,,,,,,,, 4 | 2023-01-01,WELL,WELSPECS,NODE,,,,,,,,, 5 | 2023-01-01,FIELD,BRANPROP,,,,,,,,,, 6 | 2023-01-01,NODE,BRANPROP,FIELD,9999.0,0.0,NONE,25.0,NO,NO,,,PROD 7 | -------------------------------------------------------------------------------- /tests/data/realizations.csv: -------------------------------------------------------------------------------- 1 | ENSEMBLE,REAL,SENSCASE,SENSNAME,RUNPATH 2 | iter-0,0,,,/scratch/3_r001_reek/realization-0/iter-0 3 | iter-0,1,,,/scratch/3_r001_reek/realization-1/iter-0 4 | iter-0,2,,,/scratch/3_r001_reek/realization-2/iter-0 5 | iterdm,0,p10_p90,rms_seed,/mnt/media/scratch/2_r001_reek_fullmatrix/realization-0/iter-0 6 | iterdm,1,p10_p90,rms_seed,/mnt/media/scratch/2_r001_reek_fullmatrix/realization-1/iter-0 7 | iterdm,10,deep,fwl,/scratch/2_r001_reek_fullmatrix/realization-10/iter-0 8 | iterdm,11,deep,fwl,/scratch/2_r001_reek_fullmatrix/realization-11/iter-0 9 | iterdm,12,deep,fwl,/scratch/2_r001_reek_fullmatrix/realization-12/iter-0 10 | iterdm,20,shallow,fwl,/scratch/2_r001_reek_fullmatrix/realization-20/iter-0 11 | iterdm,21,shallow,fwl,/scratch/2_r001_reek_fullmatrix/realization-21/iter-0 12 | iterdm,22,shallow,fwl,/scratch/2_r001_reek_fullmatrix/realization-22/iter-0 13 | -------------------------------------------------------------------------------- /tests/data/surface_png.txt: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAsCAAAAABTHIflAAACUUlEQVR4nI2Uy2pUQRCG/6rucxtnJpmEREHMWnwR167cuNCdG0ERMSISdSFeli7FJ1CEQFZ5Cp/BhcRoJmbOmXPtrnIhTp8xBFKrLr7+qujuooFzx6xeSrmffG38mfATAe4M+EbE0pJp/y1e+a6oWo2UTsOdWvRwXG+15rT5uPGdq+pBuzLswb9FHqnWjbdGrl5ek+tL5rZAXNV6a/13LWnJ3BZAmgPnRyzZxojcs2A+7ADVtv6pNu1MUXrtlfVKUGpm39bbqCJufTg6w3vvxHWlq5u8+l3M8qNbAb53Xec6dq4oy2Z2eFRpHSDEifPK1M27pp7+KkXvBqhEYLESO3FtWeTWmAA/EEhJV2M4rcquEOjLcAmePQaT1seCtvXzYq0pFiY+zqvKTdYvDQWmayUv2x7EbJ7PL0xGw4gS9c3xdJrfCa/SKihLCams5CdV8qOULJj7ZNxcDWySrI9jdzw9OLkZ3jOGNio2IT9BVFJTDpqFiT026JooSaN4vLExSg1zgICJuHNxmthkOMlGWRLfDmMizMa7iMDwJJE1ysHcUyLjO7VZmq6usHdi7vVGU2G818hkLsOs8GxN6LmrMKwOdjgabF65yKSmP/EkqkzJcBxla1vjTO1OgF+gCoqTJItNtnVtE2RCT5AXxGkUUYskifMGUa+sKtiysc6TZSIjT3rwM4jYGAAMC3Vve0cBFFCQikKVn/evD8CuqIhAyJG7j/8gCAQRUsMPcAoqMzMA83Sxuxc3huNRXL1b5Eu/iRCUAluGuyB6gTMgCK9x3vgDYDkpfxsK59MAAAAASUVORK5CYII= -------------------------------------------------------------------------------- /tests/data/surface_zarr.np.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/data/surface_zarr.np.gz -------------------------------------------------------------------------------- /tests/data/vfp.arrow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/data/vfp.arrow -------------------------------------------------------------------------------- /tests/integration_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/integration_tests/__init__.py -------------------------------------------------------------------------------- /tests/integration_tests/test_parameter_filter.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from webviz_subsurface._components.parameter_filter import ParameterFilter 4 | 5 | 6 | def test_dataframe(testdata_folder) -> None: 7 | # pylint: disable=protected-access 8 | dframe = pd.read_csv( 9 | testdata_folder / "reek_test_data" / "aggregated_data" / "parameters.csv" 10 | ) 11 | 12 | expected_discrete_parameters = [ 13 | "FWL", 14 | "MULTFLT_F1", 15 | "INTERPOLATE_WO", 16 | "COHIBA_MODEL_MODE", 17 | "RMS_SEED", 18 | ] 19 | 20 | component = ParameterFilter("test", dframe, include_sens_filter=False) 21 | assert set(component._discrete_parameters) == set(expected_discrete_parameters) 22 | 23 | component = ParameterFilter("test", dframe, include_sens_filter=True) 24 | assert set(component._discrete_parameters) == set( 25 | expected_discrete_parameters + ["SENSNAME"] 26 | ) 27 | 28 | assert component.is_sensitivity_run is True 29 | -------------------------------------------------------------------------------- /tests/integration_tests/test_surface_selector.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import dash 4 | import pandas as pd 5 | from dash.dependencies import Input, Output 6 | 7 | from webviz_subsurface._private_plugins.surface_selector import SurfaceSelector 8 | 9 | surface_context = { 10 | "oilthickness": { 11 | "names": ["lowerreek", "upperreek", "all", "midreek"], 12 | "dates": [ 13 | "20030101_20010601", 14 | "20010601_20000101", 15 | "20010601", 16 | "20000101", 17 | "20030101", 18 | "20030101_20000101", 19 | "20010601_20010604", 20 | ], 21 | }, 22 | "ds_extracted_horizons": { 23 | "names": ["toplowerreek", "topmidreek", "topupperreek", "baselowerreek"], 24 | "dates": [None], 25 | }, 26 | } 27 | 28 | return_value = { 29 | "name": "lowerreek", 30 | "attribute": "oilthickness", 31 | "date": "20030101_20010601", 32 | } 33 | 34 | 35 | def test_surface_selector(dash_duo: dash.testing.composite.DashComposite) -> None: 36 | app = dash.Dash(__name__) 37 | app.config.suppress_callback_exceptions = True 38 | realizations = pd.read_csv("tests/data/realizations.csv") 39 | surface_selector = SurfaceSelector(app, surface_context, realizations) 40 | 41 | app.layout = dash.html.Div( 42 | children=[surface_selector.layout, dash.html.Pre(id="pre", children="ok")] 43 | ) 44 | 45 | @app.callback( 46 | Output("pre", "children"), [Input(surface_selector.storage_id, "data")] 47 | ) 48 | def _test(data: str) -> str: 49 | return json.dumps(json.loads(data)) 50 | 51 | dash_duo.start_server(app) 52 | 53 | dash_duo.wait_for_contains_text("#pre", json.dumps(return_value), timeout=4) 54 | -------------------------------------------------------------------------------- /tests/unit_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/abbreviations_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/abbreviations_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/data_input/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/data_input/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/data_input/test_image_processing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from webviz_subsurface._datainput.image_processing import array_to_png 4 | 5 | with open("tests/data/surface_png.txt", "r") as file: 6 | BASE64_SURFACE = file.read() 7 | 8 | 9 | def test_array_to_png() -> None: 10 | data = np.loadtxt("tests/data/surface_zarr.np.gz") 11 | assert array_to_png(data) == BASE64_SURFACE 12 | -------------------------------------------------------------------------------- /tests/unit_tests/mocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/mocks/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/model_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/model_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/model_tests/test_property_statistics_model.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | from webviz_config.themes import default_theme 5 | 6 | from webviz_subsurface.plugins._property_statistics.models.property_statistics_model import ( 7 | PropertyStatisticsModel, 8 | ) 9 | 10 | 11 | def get_data_df(testdata_folder: Path) -> pd.DataFrame: 12 | return pd.read_csv( 13 | testdata_folder 14 | / "reek_test_data" 15 | / "aggregated_data" 16 | / "property_statistics.csv" 17 | ) 18 | 19 | 20 | def test_init(testdata_folder: Path) -> None: 21 | data_df = get_data_df(testdata_folder) 22 | model = PropertyStatisticsModel(dataframe=data_df, theme=default_theme) 23 | assert set(model.dataframe.columns) == set( 24 | [ 25 | "PROPERTY", 26 | "ZONE", 27 | "REGION", 28 | "FACIES", 29 | "Avg", 30 | "Avg_Weighted", 31 | "Max", 32 | "Min", 33 | "P10", 34 | "P90", 35 | "Stddev", 36 | "SOURCE", 37 | "ID", 38 | "REAL", 39 | "ENSEMBLE", 40 | "label", 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /tests/unit_tests/model_tests/test_surface_set_model.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import xtgeo 5 | 6 | from webviz_subsurface._datainput.fmu_input import find_surfaces 7 | from webviz_subsurface._models.surface_set_model import SurfaceSetModel 8 | 9 | 10 | @pytest.mark.usefixtures("app") 11 | def test_surface_set_model(testdata_folder): 12 | ensemble_paths = { 13 | "iter-0": str( 14 | Path(testdata_folder / "01_drogon_ahm" / "realization-*" / "iter-0") 15 | ) 16 | } 17 | 18 | surface_table = find_surfaces(ensemble_paths) 19 | surface_table = surface_table.drop("ENSEMBLE", axis=1) 20 | 21 | smodel = SurfaceSetModel(surface_table) 22 | assert set(smodel.attributes) == set( 23 | [ 24 | "ds_extract_postprocess", 25 | "amplitude_mean", 26 | "ds_extract_geogrid", 27 | "amplitude_rms", 28 | "oilthickness", 29 | ] 30 | ) 31 | assert set(smodel.names_in_attribute("ds_extract_postprocess")) == set( 32 | ["basevolantis", "topvolantis", "toptherys", "topvolon"] 33 | ) 34 | real_surf = smodel.get_realization_surface( 35 | attribute="ds_extract_postprocess", name="topvolon", realization=0 36 | ) 37 | assert isinstance(real_surf, xtgeo.RegularSurface) 38 | assert real_surf.values.mean() == pytest.approx(1735.42, 0.00001) 39 | stat_surf = smodel.calculate_statistical_surface( 40 | attribute="ds_extract_postprocess", name="topvolon" 41 | ) 42 | assert isinstance(stat_surf, xtgeo.RegularSurface) 43 | assert stat_surf.values.mean() == pytest.approx(1741.04, 0.00001) 44 | 45 | stat_surf = smodel.calculate_statistical_surface( 46 | attribute="ds_extract_postprocess", name="topvolon", realizations=[0, 1] 47 | ) 48 | assert stat_surf.values.mean() == pytest.approx(1741.04, 0.00001) 49 | -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/plugin_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/plugin_tests/test_simulation_time_series/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/mocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/plugin_tests/test_simulation_time_series/mocks/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/mocks/derived_vectors_accessor_ensemble_summary_provider_mock.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Sequence 2 | 3 | import pandas as pd 4 | 5 | from webviz_subsurface._providers import Frequency 6 | 7 | from ....mocks.ensemble_summary_provider_dummy import EnsembleSummaryProviderDummy 8 | 9 | 10 | class EnsembleSummaryProviderMock(EnsembleSummaryProviderDummy): 11 | """Mock implementation of EnsembleSummaryProvider for testing derived 12 | ensemble vectors accessor. 13 | 14 | Implements necessary methods for obtaining wanted test data 15 | """ 16 | 17 | def __init__(self, df: pd.DataFrame) -> None: 18 | super().__init__() 19 | self._df = df 20 | self._vectors: List[str] = [ 21 | elm for elm in df.columns if elm not in ["DATE", "REAL"] 22 | ] 23 | self._realizations: List[int] = list(df["REAL"]) if "REAL" in df.columns else [] 24 | 25 | ##################################### 26 | # 27 | # Override methods 28 | # 29 | ##################################### 30 | def supports_resampling(self) -> bool: 31 | return False 32 | 33 | def realizations(self) -> List[int]: 34 | return self._realizations 35 | 36 | def vector_names(self) -> List[str]: 37 | return self._vectors 38 | 39 | def get_vectors_df( 40 | self, 41 | vector_names: Sequence[str], 42 | __resampling_frequency: Optional[Frequency], 43 | realizations: Optional[Sequence[int]] = None, 44 | ) -> pd.DataFrame: 45 | for elm in vector_names: 46 | if elm not in self._vectors: 47 | raise ValueError( 48 | f'Requested vector "{elm}" not among provider vectors!' 49 | ) 50 | if realizations: 51 | # Note: Reset index as providers reset index counter when filtering 52 | # realization query. 53 | output = ( 54 | self._df[["DATE", "REAL"] + list(vector_names)] 55 | .loc[self._df["REAL"].isin(realizations)] 56 | .reset_index() 57 | ) 58 | output.drop("index", inplace=True, axis=1) 59 | return output 60 | return self._df[["DATE", "REAL"] + list(vector_names)] 61 | -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_datetime_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | 5 | # pylint: disable=line-too-long 6 | from webviz_subsurface.plugins._simulation_time_series._views._subplot_view._utils.datetime_utils import ( 7 | from_str, 8 | to_str, 9 | ) 10 | 11 | 12 | def test_from_str_success() -> None: 13 | assert from_str("2021-03-11") == datetime.datetime(2021, 3, 11) 14 | assert from_str("1956-08-26") == datetime.datetime(1956, 8, 26) 15 | 16 | 17 | def test_from_str_assert() -> None: 18 | # Invalid datetime arguments (hour, minute, second, microsecond) 19 | invalid_dates = ["2021-03-11-23-55-11", "1996-05-26-23", "2001-08-11-11-43"] 20 | for _date in invalid_dates: 21 | with pytest.raises(ValueError) as err: 22 | from_str(_date) 23 | assert str(err.value) == f"unconverted data remains: {_date[10:]}" 24 | 25 | 26 | def test_to_str_success() -> None: 27 | assert to_str(datetime.datetime(2021, 6, 13)) == "2021-06-13" 28 | assert to_str(datetime.datetime(2021, 12, 28)) == "2021-12-28" 29 | assert to_str(datetime.datetime(2021, 3, 7, 0)) == "2021-03-07" 30 | assert to_str(datetime.datetime(2021, 10, 22, 0, 0)) == "2021-10-22" 31 | assert to_str(datetime.datetime(2021, 1, 23, 0, 0, 0)) == "2021-01-23" 32 | assert to_str(datetime.datetime(2021, 12, 28, 0, 0, 0, 0)) == "2021-12-28" 33 | 34 | 35 | def test_to_str_assert() -> None: 36 | # Invalid datetime arguments (hour, minute, second, microsecond) 37 | invalid_dates = [ 38 | datetime.datetime(2021, 6, 13, 15, 32, 11, 43), 39 | datetime.datetime(2021, 6, 13, 5, 21, 45), 40 | datetime.datetime(2021, 6, 13, 23, 55), 41 | datetime.datetime(2021, 6, 13, 5), 42 | ] 43 | 44 | for _date in invalid_dates: 45 | with pytest.raises(ValueError) as err: 46 | to_str(_date) 47 | assert ( 48 | str(err.value) 49 | == f"Invalid date resolution, expected no data for hour, minute, second" 50 | f" or microsecond for {str(_date)}" 51 | ) 52 | -------------------------------------------------------------------------------- /tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_trace_line_shape.py: -------------------------------------------------------------------------------- 1 | from webviz_subsurface._providers import VectorMetadata 2 | 3 | # pylint: disable=line-too-long 4 | from webviz_subsurface.plugins._simulation_time_series._views._subplot_view._utils.trace_line_shape import ( 5 | get_simulation_line_shape, 6 | ) 7 | 8 | 9 | def test_get_simulation_line_shape() -> None: 10 | total_vector_metadata = VectorMetadata( 11 | unit="M3", 12 | is_total=True, 13 | is_rate=False, 14 | is_historical=False, 15 | keyword="Test", 16 | wgname=None, 17 | get_num=None, 18 | ) 19 | 20 | rate_vector_metadata = VectorMetadata( 21 | unit="M3/Day", 22 | is_total=False, 23 | is_rate=True, 24 | is_historical=False, 25 | keyword="Test rate", 26 | wgname=None, 27 | get_num=None, 28 | ) 29 | 30 | fallthrough_vector_metadata = VectorMetadata( 31 | unit="M3/M3", 32 | is_total=False, 33 | is_rate=False, 34 | is_historical=False, 35 | keyword="Test fallthrough", 36 | wgname=None, 37 | get_num=None, 38 | ) 39 | 40 | assert get_simulation_line_shape("Fallback", "PER_INTVL_vector", None) == "hv" 41 | assert get_simulation_line_shape("Fallback", "PER_DAY_vector", None) == "hv" 42 | assert get_simulation_line_shape("Fallback", "test_vector", None) == "Fallback" 43 | assert ( 44 | get_simulation_line_shape("Fallback", "test_vector", total_vector_metadata) 45 | == "linear" 46 | ) 47 | assert ( 48 | get_simulation_line_shape("Fallback", "test_vector", rate_vector_metadata) 49 | == "vh" 50 | ) 51 | assert ( 52 | get_simulation_line_shape( 53 | "Fallback", "test_vector", fallthrough_vector_metadata 54 | ) 55 | == "Fallback" 56 | ) 57 | -------------------------------------------------------------------------------- /tests/unit_tests/provider_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/provider_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/utils_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/utils_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/utils_tests/test_ensemble_summary_provider_set/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/tests/unit_tests/utils_tests/test_ensemble_summary_provider_set/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/utils_tests/test_formatting.py: -------------------------------------------------------------------------------- 1 | from webviz_subsurface._utils.formatting import printable_int_list 2 | 3 | 4 | def test_printable_int_list() -> None: 5 | assert printable_int_list([1, 2, 5, 6, 9, 8, 19]) == "1-2, 5-6, 8-9, 19" 6 | assert printable_int_list([5, 3, 4, 0]) == "0, 3-5" 7 | assert printable_int_list([0, 1, 2, 3, 6]) == "0-3, 6" 8 | assert printable_int_list([]) == "None" 9 | assert printable_int_list(None) == "None" 10 | assert printable_int_list([5]) == "5" 11 | -------------------------------------------------------------------------------- /webviz_subsurface/_abbreviations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_abbreviations/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_abbreviations/abbreviation_data/reservoir_simulation_unit_terminology.json: -------------------------------------------------------------------------------- 1 | { 2 | "METRIC": { 3 | "M": "m", 4 | "M3": "m³", 5 | "SECONDS": "seconds", 6 | "DAYS": "days", 7 | "YEARS": "years", 8 | "KG/M3": "kg/m³", 9 | "BARSA": "bara", 10 | "bars": "bar", 11 | "K": "K", 12 | "C": "\u00B0C", 13 | "CP": "cP", 14 | "MD": "mD", 15 | "SM3": "Sm³", 16 | "RM3": "Rm³", 17 | "SM3/DAY": "Sm³/day", 18 | "RM3/DAY": "Rm³/day", 19 | "CPR3/DAY/BARS": "Rm³\u00D7cP/day/bar", 20 | "MDM": "mD\u00D7m", 21 | "KG": "kg", 22 | "KG/DAY": "kg/day", 23 | "SM3/SM3": "Sm³/Sm³", 24 | "RM3/SM3": "Rm³/Sm³", 25 | "SM3/RM3": "Sm³/Rm³", 26 | "SM3/DAY/BARS": "Sm³/day/bar", 27 | "SM3/D/B": "Sm³/day/bar", 28 | "KJ": "kJ", 29 | "KJ/DAY": "kJ/day", 30 | "SEC/D": "seconds/day" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webviz_subsurface/_abbreviations/abbreviation_data/si_prefixes.json: -------------------------------------------------------------------------------- 1 | { 2 | "24": "Y", 3 | "21": "Z", 4 | "18": "E", 5 | "15": "P", 6 | "12": "T", 7 | "9": "G", 8 | "6": "M", 9 | "3": "k", 10 | "0": "", 11 | "-3": "m", 12 | "-6": "\u03BC", 13 | "-9": "n", 14 | "-12": "p", 15 | "-15": "f", 16 | "-18": "a", 17 | "-21": "z", 18 | "-24": "y" 19 | } 20 | -------------------------------------------------------------------------------- /webviz_subsurface/_abbreviations/volume_terminology.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pathlib 3 | 4 | _DATA_PATH = pathlib.Path(__file__).parent.absolute() / "abbreviation_data" 5 | 6 | VOLUME_TERMINOLOGY = json.loads((_DATA_PATH / "volume_terminology.json").read_text()) 7 | 8 | 9 | def volume_description(column: str) -> str: 10 | """Return description for the column if defined""" 11 | try: 12 | label = VOLUME_TERMINOLOGY[column]["label"] 13 | except KeyError: 14 | label = column 15 | return label 16 | 17 | 18 | def volume_unit(column: str) -> str: 19 | """Return unit for the column if defined""" 20 | try: 21 | unit = VOLUME_TERMINOLOGY[column]["unit"] 22 | except KeyError: 23 | unit = "" 24 | return unit 25 | 26 | 27 | def volume_simulation_vector_match(column: str) -> list: 28 | """Return a list of simulation vectors that match the column 29 | Useful to verify/propose data to compare. 30 | """ 31 | try: 32 | vectors = VOLUME_TERMINOLOGY[column]["eclsum"] 33 | except KeyError: 34 | vectors = [] 35 | return vectors if isinstance(vectors, list) else [vectors] 36 | -------------------------------------------------------------------------------- /webviz_subsurface/_assets/css/block_options.css: -------------------------------------------------------------------------------- 1 | .block-options > label, legend { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /webviz_subsurface/_assets/css/container.css: -------------------------------------------------------------------------------- 1 | .framed { 2 | border-radius: 5px; 3 | background-color: #F9F9F9; 4 | margin: 10px; 5 | padding: 15px; 6 | position: relative; 7 | box-shadow: 2px 2px 2px lightgrey; 8 | } 9 | 10 | .framed:hover { 11 | box-shadow: 5px 5px 5px 3px lightgrey; 12 | } 13 | -------------------------------------------------------------------------------- /webviz_subsurface/_assets/css/inplace_volumes.css: -------------------------------------------------------------------------------- 1 | .webviz-inplace-vol-btn { 2 | background-color: #E8E8E8; 3 | width: 100%; 4 | font-size: 12px; 5 | height: 30px; 6 | line-height:30px; 7 | padding:0px; 8 | } 9 | 10 | 11 | .webviz-inplace-vol-btn:hover { 12 | box-shadow: 0px 0px 3px 0.5px lightgrey; 13 | } 14 | -------------------------------------------------------------------------------- /webviz_subsurface/_assets/css/structural_uncertainty.css: -------------------------------------------------------------------------------- 1 | .webviz-structunc-uncertainty-table-label { 2 | flex: 1; 3 | font-size: 1.2em; 4 | line-height: 1.6; 5 | text-align: middle; 6 | min-width: 100px; 7 | } 8 | 9 | .webviz-structunc-uncertainty-table-apply-btn { 10 | background-color: #7393B3; 11 | border-color: #7393B3; 12 | color: #fff; 13 | display: inline; 14 | margin-left: auto; 15 | margin-right: auto; 16 | flex: 1; 17 | min-width: 100px; 18 | } 19 | 20 | .webviz-structunc-uncertainty-table-wrapper { 21 | overflow-y: auto; 22 | height: 90vh; 23 | font-size: 0.8em; 24 | } 25 | 26 | .webviz-structunc-blue-apply-btn { 27 | background-color: #E8E8E8; 28 | width: 100%; 29 | display: block; 30 | padding: 0 20px; 31 | margin-top: 20px; 32 | margin-bottom: 5px; 33 | margin-left: auto; 34 | margin-right: auto; 35 | } 36 | 37 | .webviz-structunc-range-input { 38 | background-color: #fff; 39 | width: 30%; 40 | border-width: thin; 41 | position: absolute; 42 | right: 15px; 43 | margin-right: 10px; 44 | } 45 | 46 | .webviz-structunc-range-label { 47 | display: inline; 48 | margin-right: 10; 49 | } 50 | 51 | 52 | 53 | .webviz-structunc-details-main { 54 | cursor: pointer; 55 | color: #ff1243; 56 | border-bottom-style: solid; 57 | border-width: thin; 58 | border-color: #ff1243; 59 | font-weight: bold; 60 | font-size: 20px; 61 | margin-bottom: 15px; 62 | } 63 | 64 | .webviz-structunc-settings { 65 | cursor: pointer; 66 | color: #555; 67 | margin-top: 5px; 68 | padding-top: 5px; 69 | } 70 | .webviz-structunc-settings:hover { 71 | box-shadow: 0px 0px 3px 0.5px lightgrey; 72 | } 73 | 74 | 75 | 76 | .webviz-structunc-blue-apply-btn:hover { 77 | box-shadow: 0px 0px 3px 0.5px lightgrey; 78 | } 79 | 80 | .leaflet-container { 81 | background: transparent !important; 82 | } 83 | -------------------------------------------------------------------------------- /webviz_subsurface/_assets/js/clientside_functions.js: -------------------------------------------------------------------------------- 1 | window.dash_clientside = Object.assign({}, window.dash_clientside, { 2 | clientside: { 3 | set_dcc_figure: function (layout, data) { 4 | /* 5 | Can be used in a dash callback to update 6 | the `figure` prop on a dcc.Graph component, 7 | e.g. to update the `layout` clientside without 8 | sending the `data` from the backend. 9 | */ 10 | return { data: data, layout: layout }; 11 | }, 12 | get_client_height: function (_triggered) { 13 | /* 14 | Can be used in a dash callback to get the clientHeight 15 | in pixels 16 | */ 17 | return document.documentElement.clientHeight 18 | } 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /webviz_subsurface/_components/__init__.py: -------------------------------------------------------------------------------- 1 | from .color_picker import ColorPicker 2 | from .tornado.tornado_widget import TornadoWidget 3 | -------------------------------------------------------------------------------- /webviz_subsurface/_components/tornado/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_components/tornado/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_datainput/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/eclipse_init_io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_datainput/eclipse_init_io/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/grid.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import xtgeo 4 | from webviz_config.common_cache import CACHE 5 | 6 | 7 | @CACHE.memoize() 8 | def load_grid(gridpath: str) -> xtgeo.Grid: 9 | return xtgeo.grid_from_file(gridpath) 10 | 11 | 12 | @CACHE.memoize() 13 | def load_grid_parameter( 14 | grid: Optional[xtgeo.Grid], gridparameterpath: str 15 | ) -> xtgeo.GridProperty: 16 | return xtgeo.gridproperty_from_file(gridparameterpath, grid=grid) 17 | -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/inplace_volumes.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Dict 3 | 4 | import pandas as pd 5 | from webviz_config.common_cache import CACHE 6 | from webviz_config.webviz_store import webvizstore 7 | 8 | from .fmu_input import scratch_ensemble 9 | 10 | 11 | @CACHE.memoize() 12 | @webvizstore 13 | def extract_volumes( 14 | ensemble_paths: dict, volfolder: str, volfiles: Dict[str, str] 15 | ) -> pd.DataFrame: 16 | """Aggregates volumetric files from an FMU ensemble. 17 | Files must be stored on standardized csv format. 18 | """ 19 | dfs = [] 20 | for ens_name, ens_path in ensemble_paths.items(): 21 | ens_dfs = [] 22 | ens = scratch_ensemble(ens_name, ens_path) 23 | for volname, volfile in volfiles.items(): 24 | try: 25 | path = os.path.join(volfolder, volfile) 26 | df = ens.load_csv(path) 27 | df["SOURCE"] = volname 28 | df["ENSEMBLE"] = ens_name 29 | ens_dfs.append(df) 30 | except KeyError: 31 | pass 32 | try: 33 | dfs.append(pd.concat(ens_dfs)) 34 | except ValueError: 35 | pass 36 | if not dfs: 37 | raise ValueError( 38 | f"Error when aggregating inplace volumetric files: {list(volfiles)}. " 39 | f"Ensure that the files are present in relative folder {volfolder}" 40 | ) 41 | return pd.concat(dfs) 42 | -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/relative_permeability.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Optional, Union 3 | 4 | import pandas as pd 5 | from webviz_config.webviz_store import webvizstore 6 | 7 | from .fmu_input import load_ensemble_set 8 | 9 | try: 10 | import res2df 11 | except ImportError: 12 | pass 13 | 14 | try: 15 | from pyscal import PyscalFactory 16 | except ImportError: 17 | pass 18 | 19 | 20 | @webvizstore 21 | def load_satfunc( 22 | ensemble_paths: dict, 23 | ensemble_set_name: str = "EnsembleSet", 24 | ) -> pd.DataFrame: 25 | def res2df_satfunc(kwargs: Any) -> pd.DataFrame: 26 | return res2df.satfunc.df(kwargs["realization"].get_eclfiles()) 27 | 28 | return load_ensemble_set(ensemble_paths, ensemble_set_name).apply(res2df_satfunc) 29 | 30 | 31 | @webvizstore 32 | def load_scal_recommendation( 33 | scalfile: Path, sheet_name: Optional[Union[str, int, list]] = None 34 | ) -> pd.DataFrame: 35 | return ( 36 | PyscalFactory.create_scal_recommendation_list( 37 | PyscalFactory.load_relperm_df(str(scalfile), sheet_name) 38 | ) 39 | .df() 40 | .replace( 41 | { 42 | "CASE": { 43 | "(?i)low": "pess", 44 | r"(?i)^pes.*": "pess", 45 | "(?i)base": "base", 46 | "(?i)high": "opt", 47 | r"(?i)^opt.*": "opt", 48 | } 49 | }, 50 | regex=True, 51 | ) 52 | ) 53 | -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/seismic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xtgeo 3 | from webviz_config.common_cache import CACHE 4 | 5 | 6 | @CACHE.memoize() 7 | def load_cube_data(cube_path: str) -> xtgeo.Cube: 8 | return xtgeo.cube_from_file(cube_path) 9 | 10 | 11 | @CACHE.memoize() 12 | def get_xline(cube: xtgeo.Cube, xline: int) -> np.ndarray: 13 | idx = np.where(cube.xlines == xline) 14 | return cube.values[:, idx, :][:, 0, 0].T 15 | 16 | 17 | @CACHE.memoize() 18 | def get_iline(cube: xtgeo.Cube, iline: int) -> np.ndarray: 19 | idx = np.where(cube.ilines == iline) 20 | return cube.values[idx, :, :][0, 0, :].T 21 | 22 | 23 | @CACHE.memoize() 24 | def get_zslice(cube: xtgeo.Cube, zslice: float) -> np.ndarray: 25 | idx = np.where(cube.zslices == zslice) 26 | return cube.values[:, :, idx][:, :, 0, 0].T 27 | -------------------------------------------------------------------------------- /webviz_subsurface/_datainput/surface.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xtgeo 3 | from webviz_config.common_cache import CACHE 4 | 5 | 6 | @CACHE.memoize() 7 | def load_surface(surface_path: str) -> xtgeo.RegularSurface: 8 | return xtgeo.surface_from_file(surface_path) 9 | 10 | 11 | @CACHE.memoize() 12 | def get_surface_fence(fence: np.ndarray, surface: xtgeo.RegularSurface) -> np.ndarray: 13 | return surface.get_fence(fence) 14 | -------------------------------------------------------------------------------- /webviz_subsurface/_figures/__init__.py: -------------------------------------------------------------------------------- 1 | from .barchart import BarChart 2 | from .px_figure import create_figure 3 | from .scatterplot import ScatterPlot 4 | from .timeseries_figure import TimeSeriesFigure 5 | -------------------------------------------------------------------------------- /webviz_subsurface/_models/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_model import EnsembleModel 2 | from .ensemble_set_model import EnsembleSetModel 3 | from .gruptree_model import GruptreeModel 4 | from .inplace_volumes_model import InplaceVolumesModel 5 | from .observation_model import ObservationModel 6 | from .parameter_model import ParametersModel 7 | from .stratigraphy_model import StratigraphyModel 8 | from .surface_leaflet_model import SurfaceLeafletModel 9 | from .surface_set_model import SurfaceSetModel 10 | from .well_attributes_model import WellAttributesModel 11 | from .well_set_model import WellSetModel 12 | -------------------------------------------------------------------------------- /webviz_subsurface/_models/caching_ensemble_set_model_factory.py: -------------------------------------------------------------------------------- 1 | import json 2 | import threading 3 | from typing import Dict, Optional, Union 4 | 5 | from .ensemble_set_model import EnsembleSetModel 6 | 7 | # Module level globals 8 | # Do we actually need to consider locking here or will this access always be single threaded? 9 | _cache_lock = threading.Lock() 10 | _ensemble_set_model_cache: Dict[str, EnsembleSetModel] = {} 11 | 12 | 13 | def get_or_create_model( 14 | ensemble_paths: dict, 15 | time_index: Optional[Union[list, str]] = None, 16 | column_keys: Optional[list] = None, 17 | ) -> EnsembleSetModel: 18 | modelkey = json.dumps( 19 | { 20 | "ensemble_paths": ensemble_paths, 21 | "time_index": time_index, 22 | "column_keys": column_keys, 23 | } 24 | ) 25 | 26 | with _cache_lock: 27 | if modelkey in _ensemble_set_model_cache: 28 | # Just return existing model from cache 29 | return _ensemble_set_model_cache[modelkey] 30 | 31 | # No matching model in cache -> create a new ensemble set model and insert in cache 32 | new_model = EnsembleSetModel( 33 | ensemble_paths=ensemble_paths, 34 | smry_time_index=time_index, 35 | smry_column_keys=column_keys, 36 | ) 37 | _ensemble_set_model_cache[modelkey] = new_model 38 | 39 | return new_model 40 | -------------------------------------------------------------------------------- /webviz_subsurface/_models/observation_model.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Dict, List, Optional 3 | 4 | import yaml 5 | 6 | 7 | class ObservationModel: 8 | def __init__( 9 | self, 10 | observation_file: Path = None, 11 | observation_key: str = "general", 12 | remap_key: Dict[str, str] = None, 13 | remap_value: Dict[str, str] = None, 14 | ) -> None: 15 | self.observations = self.load_yaml(observation_file) if observation_file else {} 16 | self.observation_group = observation_key 17 | self.remap_key = remap_key if remap_key else {} 18 | self.remap_value = remap_value if remap_value else {} 19 | 20 | @staticmethod 21 | def load_yaml(yaml_file: Path) -> Dict: 22 | with open(Path(yaml_file), "r") as stream: 23 | return yaml.safe_load(stream) 24 | 25 | def get_attributes(self) -> List: 26 | return [ 27 | attribute["key"] 28 | for attribute in self.observations.get(self.observation_group, []) 29 | ] 30 | 31 | def get_observations_for_attribute( 32 | self, attribute: str, value: str 33 | ) -> Optional[List]: 34 | for attr in self.observations.get(self.observation_group, []): 35 | if str(attr["key"]) == self.remap_key.get(attribute, attribute): 36 | observations = [] 37 | for obs in attr.get("observations", []): 38 | observations.append( 39 | { 40 | "value": obs.get("value", None), 41 | "error": obs.get("error", None), 42 | value: obs.get(self.remap_value.get(value, value), None), 43 | } 44 | ) 45 | return observations 46 | return None 47 | -------------------------------------------------------------------------------- /webviz_subsurface/_private_plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_private_plugins/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_providers/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_fault_polygons_provider import ( 2 | EnsembleFaultPolygonsProvider, 3 | EnsembleFaultPolygonsProviderFactory, 4 | FaultPolygonsAddress, 5 | FaultPolygonsServer, 6 | SimulatedFaultPolygonsAddress, 7 | ) 8 | from .ensemble_summary_provider.ensemble_summary_provider import ( 9 | EnsembleSummaryProvider, 10 | Frequency, 11 | VectorMetadata, 12 | ) 13 | from .ensemble_summary_provider.ensemble_summary_provider_factory import ( 14 | EnsembleSummaryProviderFactory, 15 | ) 16 | from .ensemble_summary_provider.utils import get_matching_vector_names 17 | from .ensemble_surface_provider import ( 18 | EnsembleSurfaceProvider, 19 | EnsembleSurfaceProviderFactory, 20 | ObservedSurfaceAddress, 21 | QualifiedDiffSurfaceAddress, 22 | QualifiedSurfaceAddress, 23 | SimulatedSurfaceAddress, 24 | StatisticalSurfaceAddress, 25 | SurfaceAddress, 26 | SurfaceArrayMeta, 27 | SurfaceArrayServer, 28 | SurfaceImageMeta, 29 | SurfaceImageServer, 30 | ) 31 | from .ensemble_table_provider import ( 32 | ColumnMetadata, 33 | EnsembleTableProvider, 34 | EnsembleTableProviderFactory, 35 | EnsembleTableProviderImplArrow, 36 | ) 37 | from .well_provider import WellProvider, WellProviderFactory, WellServer 38 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_fault_polygons_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_fault_polygons_provider import ( 2 | EnsembleFaultPolygonsProvider, 3 | SimulatedFaultPolygonsAddress, 4 | ) 5 | from .ensemble_fault_polygons_provider_factory import ( 6 | EnsembleFaultPolygonsProviderFactory, 7 | ) 8 | from .fault_polygons_server import FaultPolygonsAddress, FaultPolygonsServer 9 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_fault_polygons_provider/ensemble_fault_polygons_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from dataclasses import dataclass 3 | from typing import List, Optional 4 | 5 | import xtgeo 6 | 7 | 8 | @dataclass(frozen=True) 9 | class SimulatedFaultPolygonsAddress: 10 | """Specifies a unique simulated fault polygon set for a given ensemble realization""" 11 | 12 | attribute: str 13 | name: str 14 | realization: int 15 | 16 | 17 | # Type aliases used for signature readability 18 | FaultPolygonsAddress = SimulatedFaultPolygonsAddress 19 | 20 | 21 | # Class provides data for ensemble surfaces 22 | class EnsembleFaultPolygonsProvider(abc.ABC): 23 | @abc.abstractmethod 24 | def provider_id(self) -> str: 25 | """Returns string ID of the provider.""" 26 | 27 | @abc.abstractmethod 28 | def attributes(self) -> List[str]: 29 | """Returns list of all available attributes.""" 30 | 31 | @abc.abstractmethod 32 | def fault_polygons_names_for_attribute( 33 | self, fault_polygons_attribute: str 34 | ) -> List[str]: 35 | """Returns list of all available fault polygons names for a given attribute.""" 36 | 37 | @abc.abstractmethod 38 | def realizations(self) -> List[int]: 39 | """Returns list of all available realizations.""" 40 | 41 | @abc.abstractmethod 42 | def get_fault_polygons( 43 | self, 44 | address: FaultPolygonsAddress, 45 | ) -> Optional[xtgeo.Polygons]: 46 | """Returns fault polygons for a given fault polygons address""" 47 | 48 | # @abc.abstractmethod 49 | # def get_surface_bounds(self, surface: EnsembleSurfaceContext) -> List[float]: 50 | # """Returns the bounds for a surface [xmin,ymin, xmax,ymax]""" 51 | 52 | # @abc.abstractmethod 53 | # def get_surface_value_range(self, surface: EnsembleSurfaceContext) -> List[float]: 54 | # """Returns the value range for a given surface context [zmin, zmax]""" 55 | 56 | # @abc.abstractmethod 57 | # def get_surface_as_rgba(self, surface: EnsembleSurfaceContext) -> io.BytesIO: 58 | # """Returns surface as a greyscale png RGBA with encoded elevation values 59 | # in a bytestream""" 60 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_grid_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_grid_provider import EnsembleGridProvider 2 | from .ensemble_grid_provider_factory import EnsembleGridProviderFactory 3 | from .grid_viz_service import CellFilter, GridVizService, PickResult, PropertySpec, Ray 4 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_grid_provider/ensemble_grid_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import List, Optional 3 | 4 | import numpy as np 5 | import xtgeo 6 | 7 | 8 | class EnsembleGridProvider(abc.ABC): 9 | @abc.abstractmethod 10 | def provider_id(self) -> str: 11 | """Returns string ID of the provider.""" 12 | 13 | @abc.abstractmethod 14 | def static_property_names(self) -> List[str]: 15 | """Returns list of all available static properties.""" 16 | 17 | @abc.abstractmethod 18 | def dynamic_property_names(self) -> List[str]: 19 | """Returns list of all available dynamic properties.""" 20 | 21 | @abc.abstractmethod 22 | def dates_for_dynamic_property(self, property_name: str) -> Optional[List[str]]: 23 | """Returns list of all available dates for a given dynamic property.""" 24 | 25 | @abc.abstractmethod 26 | def realizations(self) -> List[int]: 27 | """Returns list of all available realizations.""" 28 | 29 | @abc.abstractmethod 30 | def get_3dgrid( 31 | self, 32 | realization: int, 33 | ) -> xtgeo.Grid: 34 | """Returns grid for specified realization""" 35 | 36 | @abc.abstractmethod 37 | def get_static_property_values( 38 | self, property_name: str, realization: int 39 | ) -> Optional[np.ndarray]: 40 | """Returns 1d cell values for a given static property""" 41 | 42 | @abc.abstractmethod 43 | def get_dynamic_property_values( 44 | self, property_name: str, property_date: str, realization: int 45 | ) -> Optional[np.ndarray]: 46 | """Returns 1d cell values for a given dynamic property""" 47 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_polygon_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_polygon_provider import EnsemblePolygonProvider, SimulatedPolygonsAddress 2 | from .ensemble_polygon_provider_factory import EnsemblePolygonProviderFactory 3 | from .polygon_server import PolygonsAddress, PolygonServer 4 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from dataclasses import dataclass 3 | from typing import List, Optional 4 | 5 | import xtgeo 6 | 7 | 8 | @dataclass(frozen=True) 9 | class SimulatedPolygonsAddress: 10 | """Specifies a unique simulated polygon set for a given ensemble realization""" 11 | 12 | attribute: str 13 | name: str 14 | realization: int 15 | 16 | 17 | # Type aliases used for signature readability 18 | PolygonsAddress = SimulatedPolygonsAddress 19 | 20 | 21 | # Class provides data for ensemble surfaces 22 | class EnsemblePolygonProvider(abc.ABC): 23 | @abc.abstractmethod 24 | def provider_id(self) -> str: 25 | """Returns string ID of the provider.""" 26 | 27 | @abc.abstractmethod 28 | def attributes(self) -> List[str]: 29 | """Returns list of all available attributes.""" 30 | 31 | @abc.abstractmethod 32 | def realizations(self) -> List[int]: 33 | """Returns list of all available realizations.""" 34 | 35 | @abc.abstractmethod 36 | def get_polygons( 37 | self, 38 | address: PolygonsAddress, 39 | ) -> Optional[xtgeo.Polygons]: 40 | """Returns fault polygons for a given fault polygons address""" 41 | 42 | # @abc.abstractmethod 43 | # def get_surface_bounds(self, surface: EnsembleSurfaceContext) -> List[float]: 44 | # """Returns the bounds for a surface [xmin,ymin, xmax,ymax]""" 45 | 46 | # @abc.abstractmethod 47 | # def get_surface_value_range(self, surface: EnsembleSurfaceContext) -> List[float]: 48 | # """Returns the value range for a given surface context [zmin, zmax]""" 49 | 50 | # @abc.abstractmethod 51 | # def get_surface_as_rgba(self, surface: EnsembleSurfaceContext) -> io.BytesIO: 52 | # """Returns surface as a greyscale png RGBA with encoded elevation values 53 | # in a bytestream""" 54 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_summary_provider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_providers/ensemble_summary_provider/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_summary_provider/_csv_import.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from typing import Optional 4 | 5 | import pandas as pd 6 | 7 | from webviz_subsurface._utils.perf_timer import PerfTimer 8 | 9 | LOGGER = logging.getLogger(__name__) 10 | 11 | 12 | def load_ensemble_summary_csv_file( 13 | csv_file: Path, ensemble_filter: Optional[str] 14 | ) -> pd.DataFrame: 15 | LOGGER.debug(f"load_ensemble_summary_csv_file() starting - {csv_file}") 16 | timer = PerfTimer() 17 | 18 | df: pd.DataFrame = pd.read_csv(csv_file) 19 | 20 | if ensemble_filter is not None: 21 | if "ENSEMBLE" not in df.columns: 22 | raise ValueError( 23 | "Cannot filter on ensemble, no ENSEMBLE column exist in CSV file" 24 | ) 25 | 26 | df = df[df["ENSEMBLE"] == ensemble_filter] 27 | 28 | if "ENSEMBLE" in df.columns: 29 | if df["ENSEMBLE"].nunique() > 1: 30 | raise KeyError("Input data contains more than one unique ensemble name") 31 | 32 | df = df.drop(columns="ENSEMBLE") 33 | 34 | LOGGER.debug( 35 | f"load_ensemble_summary_csv_file() finished in: {timer.elapsed_s():.2f}s" 36 | ) 37 | 38 | return df 39 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_summary_provider/utils.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import re 3 | from typing import List 4 | 5 | from .ensemble_summary_provider import EnsembleSummaryProvider 6 | 7 | 8 | def get_matching_vector_names( 9 | provider: EnsembleSummaryProvider, column_keys: List[str] 10 | ) -> List[str]: 11 | """Returns a list of vectors that match the input columns_keys that 12 | can have unix shell wildcards. 13 | 14 | Example of use: 15 | column_keys = ["FOPT", "WGOR*"] 16 | matching_vector_names = get_matching_vector_names(provider, column_keys) 17 | df = provider.get_vectors_df(matching_vector_names, None) 18 | 19 | """ 20 | regex = re.compile( 21 | "|".join([fnmatch.translate(col) for col in column_keys]), 22 | flags=re.IGNORECASE, 23 | ) 24 | return [vec for vec in provider.vector_names() if regex.fullmatch(vec)] 25 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_surface_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from ._types import QualifiedDiffSurfaceAddress, QualifiedSurfaceAddress 2 | from .ensemble_surface_provider import ( 3 | EnsembleSurfaceProvider, 4 | ObservedSurfaceAddress, 5 | SimulatedSurfaceAddress, 6 | StatisticalSurfaceAddress, 7 | SurfaceAddress, 8 | ) 9 | from .ensemble_surface_provider_factory import EnsembleSurfaceProviderFactory 10 | from .surface_array_server import SurfaceArrayMeta, SurfaceArrayServer 11 | from .surface_image_server import SurfaceImageMeta, SurfaceImageServer 12 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | import numpy as np 4 | import xtgeo 5 | 6 | 7 | def surface_to_float32_array(surface: xtgeo.RegularSurface) -> io.BytesIO: 8 | values = surface.values.astype(np.float32) 9 | values.fill_value = np.nan 10 | values = np.ma.filled(values) 11 | 12 | # Rotate 90 deg left. 13 | # This will cause the width of to run along the X axis 14 | # and height of along Y axis (starting from bottom.) 15 | values = np.rot90(values) 16 | 17 | byte_io = io.BytesIO() 18 | byte_io.write(values.tobytes()) 19 | byte_io.seek(0) 20 | return byte_io 21 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_surface_provider/_types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .ensemble_surface_provider import SurfaceAddress 4 | 5 | 6 | @dataclass(frozen=True) 7 | class QualifiedSurfaceAddress: 8 | provider_id: str 9 | address: SurfaceAddress 10 | 11 | 12 | @dataclass(frozen=True) 13 | class QualifiedDiffSurfaceAddress: 14 | provider_id_a: str 15 | address_a: SurfaceAddress 16 | provider_id_b: str 17 | address_b: SurfaceAddress 18 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_table_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_table_provider import ColumnMetadata, EnsembleTableProvider 2 | from .ensemble_table_provider_factory import EnsembleTableProviderFactory 3 | from .ensemble_table_provider_impl_arrow import EnsembleTableProviderImplArrow 4 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_table_provider/_field_metadata.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pyarrow as pa 4 | 5 | from .ensemble_table_provider import ColumnMetadata 6 | 7 | 8 | def create_column_metadata_from_field_meta( 9 | field: pa.Field, 10 | ) -> Optional[ColumnMetadata]: 11 | """Create VectorMetadata from keywords stored in the field's metadata""" 12 | 13 | meta_dict = field.metadata 14 | if not meta_dict: 15 | return None 16 | 17 | try: 18 | unit_bytestr = meta_dict[b"unit"] 19 | except KeyError: 20 | return ColumnMetadata(unit=None) 21 | 22 | return ColumnMetadata( 23 | unit=unit_bytestr.decode("ascii"), 24 | ) 25 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/ensemble_table_provider/ensemble_table_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from dataclasses import dataclass 3 | from typing import List, Optional, Sequence 4 | 5 | import pandas as pd 6 | 7 | 8 | @dataclass(frozen=True) 9 | class ColumnMetadata: 10 | unit: Optional[str] 11 | 12 | 13 | class EnsembleTableProvider(abc.ABC): 14 | @abc.abstractmethod 15 | def column_names(self) -> List[str]: 16 | ... 17 | 18 | @abc.abstractmethod 19 | def realizations(self) -> List[int]: 20 | ... 21 | 22 | @abc.abstractmethod 23 | def get_column_data( 24 | self, column_names: Sequence[str], realizations: Optional[Sequence[int]] = None 25 | ) -> pd.DataFrame: 26 | ... 27 | 28 | @abc.abstractmethod 29 | def column_metadata(self, column_name: str) -> Optional[ColumnMetadata]: 30 | """Returns metadata for the specified column. 31 | 32 | Returns None if no metadata is found for the column. 33 | Returns a empty ColumnMetadata object if there is metadata, but it's 34 | not the columns specified in ColumnMetadata. 35 | """ 36 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/well_provider/__init__.py: -------------------------------------------------------------------------------- 1 | from .well_provider import WellProvider 2 | from .well_provider_factory import WellProviderFactory 3 | from .well_server import WellServer 4 | -------------------------------------------------------------------------------- /webviz_subsurface/_providers/well_provider/well_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from dataclasses import dataclass 3 | from typing import List 4 | 5 | import numpy as np 6 | import xtgeo 7 | 8 | 9 | @dataclass(frozen=True) 10 | class WellPath: 11 | x_arr: np.ndarray 12 | y_arr: np.ndarray 13 | z_arr: np.ndarray 14 | md_arr: np.ndarray 15 | 16 | 17 | # Class provides data for wells 18 | class WellProvider(abc.ABC): 19 | @abc.abstractmethod 20 | def provider_id(self) -> str: 21 | """Returns string ID of the provider.""" 22 | 23 | @abc.abstractmethod 24 | def well_names(self) -> List[str]: 25 | """Returns list of all available well names.""" 26 | 27 | @abc.abstractmethod 28 | def get_well_path(self, well_name: str) -> WellPath: 29 | """Returns the coordinates for the well path along with MD for the well.""" 30 | 31 | @abc.abstractmethod 32 | def get_well_xtgeo_obj(self, well_name: str) -> xtgeo.Well: 33 | ... 34 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/_utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/_utils/design_matrix.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pandas as pd 4 | 5 | LOGGER = logging.getLogger(__name__) 6 | 7 | 8 | def rename_design_matrix_parameter_columns(parameter_df: pd.DataFrame) -> pd.DataFrame: 9 | """Given a dataframe of parameters, checks if the DESIGN_MATRIX prefix is present. 10 | If present assume this is a design matrix run. Return the dataframe with the prefix 11 | removed. Also do a check if removing the prefix result in any duplicates. 12 | If duplicates remove those and give a warning. 13 | """ 14 | 15 | if any(col.startswith("DESIGN_MATRIX:") for col in parameter_df.columns): 16 | original_columns = parameter_df.columns 17 | stripped_columns = original_columns.str.replace( 18 | r"^DESIGN_MATRIX:", "", regex=True 19 | ) 20 | rename_map = { 21 | old: new 22 | for old, new in zip(original_columns, stripped_columns) 23 | if old != new 24 | } 25 | conflict_names = set(rename_map.values()) & set(original_columns) 26 | if conflict_names: 27 | LOGGER.info( 28 | "DESIGN_MATRIX run detected, but non design matrix parameters was found." 29 | ) 30 | LOGGER.info( 31 | f"The following parameters will be dropped: {sorted(conflict_names)}" 32 | ) 33 | parameter_df = parameter_df.drop(columns=conflict_names) 34 | 35 | parameter_df = parameter_df.rename(columns=rename_map) 36 | return parameter_df 37 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/ensemble_table_provider_set_factory.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Dict 3 | 4 | from webviz_subsurface._providers import ( 5 | EnsembleTableProvider, 6 | EnsembleTableProviderFactory, 7 | ) 8 | 9 | from .ensemble_table_provider_set import EnsembleTableProviderSet 10 | 11 | 12 | def create_csvfile_providerset_from_paths( 13 | name_path_dict: Dict[str, Path], 14 | rel_file_pattern: str, 15 | drop_failed_realizations: bool = True, 16 | ) -> EnsembleTableProviderSet: 17 | """Create set of ensemble table providers 18 | 19 | `Input:` 20 | * name_path_dict: Dict[str, Path] - ensemble name as key and ensemble path as value 21 | * rel_file_pattern: str - specify a relative (per realization) file pattern to find the 22 | wanted .csv files within each realization 23 | 24 | """ 25 | provider_factory = EnsembleTableProviderFactory.instance() 26 | provider_dict: Dict[str, EnsembleTableProvider] = {} 27 | for name, path in name_path_dict.items(): 28 | provider_dict[name] = provider_factory.create_from_per_realization_csv_file( 29 | str(path), rel_file_pattern, drop_failed_realizations 30 | ) 31 | return EnsembleTableProviderSet(provider_dict) 32 | 33 | 34 | def create_parameter_providerset_from_paths( 35 | name_path_dict: Dict[str, Path], 36 | drop_failed_realizations: bool = True, 37 | ) -> EnsembleTableProviderSet: 38 | """Create set of ensemble parametertable providers 39 | 40 | `Input:` 41 | * name_path_dict: Dict[str, Path] - ensemble name as key and ensemble path as value 42 | 43 | """ 44 | provider_factory = EnsembleTableProviderFactory.instance() 45 | provider_dict: Dict[str, EnsembleTableProvider] = {} 46 | for name, path in name_path_dict.items(): 47 | provider_dict[ 48 | name 49 | ] = provider_factory.create_from_per_realization_parameter_file( 50 | str(path), drop_failed_realizations 51 | ) 52 | return EnsembleTableProviderSet(provider_dict) 53 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/enum_shim.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info < (3, 11): 4 | from enum import Enum 5 | from enum import EnumMeta as EnumType 6 | 7 | class StrEnum(str, Enum): 8 | pass 9 | 10 | else: 11 | from enum import EnumType, StrEnum 12 | 13 | __all__ = ["StrEnum", "EnumType"] 14 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/formatting.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | 4 | def printable_int_list(integer_list: Optional[List[int]]) -> str: 5 | """Creates a string out of a list of integers. 6 | The string gives a range x-y if all the integers between and 7 | including x and y are in the list, otherwise separated by commas. 8 | Example: the list [0, 1, 2, 4, 5, 8, 10] becomes '0-2, 4-5, 8, 10' 9 | If the input is `None` or the list is empty, the string 'None' is returned. 10 | """ 11 | if not integer_list: 12 | return "None" 13 | 14 | sorted_list = sorted(integer_list) 15 | prev_number = sorted_list[0] 16 | string = str(prev_number) 17 | 18 | for next_number in sorted_list[1:]: 19 | if next_number > prev_number + 1: 20 | string += ( 21 | f"{prev_number}" if string.endswith("-") else "" 22 | ) + f", {next_number}" 23 | elif not string.endswith("-"): 24 | string += "-" 25 | prev_number = next_number 26 | 27 | if not string.endswith(f", {prev_number}") and string != str(prev_number): 28 | string += str(prev_number) 29 | return string 30 | 31 | 32 | def parse_number_from_string(value: str) -> Union[int, float, str]: 33 | """Try to parse the string first as an integer, then as float, 34 | if both fails, return the original string. 35 | """ 36 | 37 | try: 38 | return int(value) 39 | except ValueError: 40 | try: 41 | return float(value) 42 | except ValueError: 43 | return value 44 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/perf_timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class PerfTimer: 5 | def __init__(self) -> None: 6 | self._start_s = time.perf_counter() 7 | self._lap_s = self._start_s 8 | 9 | def elapsed_s(self) -> float: 10 | """Get the time elapsed since the start, in seconds""" 11 | return time.perf_counter() - self._start_s 12 | 13 | def elapsed_ms(self) -> int: 14 | """Get the time elapsed since the start, in milliseconds""" 15 | return int(1000 * self.elapsed_s()) 16 | 17 | def lap_s(self) -> float: 18 | """Get elapsed time since last lap, in seconds""" 19 | time_now = time.perf_counter() 20 | elapsed = time_now - self._lap_s 21 | self._lap_s = time_now 22 | return elapsed 23 | 24 | def lap_ms(self) -> int: 25 | """Get elapsed time since last lap, in milliseconds""" 26 | return int(1000 * self.lap_s()) 27 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/unique_theming.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from webviz_config._theme_class import WebvizConfigTheme 4 | 5 | 6 | def unique_colors( 7 | elements: Union[list, set], theme: Optional[Union[WebvizConfigTheme, dict]] = None 8 | ) -> dict: 9 | """Returns a dict with discrete colors from your theme if that is defined, 10 | otherwise from the list below. If len(elements) > available unique colors 11 | the colors will be looped (and therefore not unique...) 12 | """ 13 | if isinstance(elements, set): 14 | elements = list(elements) 15 | elif not isinstance(elements, list): 16 | raise TypeError("'elements' has to be a list or a set") 17 | 18 | if isinstance(theme, WebvizConfigTheme): 19 | theme = theme.plotly_theme 20 | elif theme is None: 21 | theme = {} 22 | # otherwise we assume that it is either a dict, a plotly layout object or some other 23 | # class with a similar .get() method. 24 | 25 | # The try-except is to handle plotly layout dicts as well as plotly themes 26 | try: 27 | colors = theme["layout"]["colorway"] 28 | except KeyError: 29 | colors = theme.get( 30 | "colorway", 31 | [ 32 | "#243746", 33 | "#eb0036", 34 | "#919ba2", 35 | "#7d0023", 36 | "#66737d", 37 | "#4c9ba1", 38 | "#a44c65", 39 | "#80b7bc", 40 | "#ff1243", 41 | "#919ba2", 42 | "#be8091", 43 | "#b2d4d7", 44 | "#ff597b", 45 | "#bdc3c7", 46 | "#d8b2bd", 47 | "#ffe7d6", 48 | "#d5eaf4", 49 | "#ff88a1", 50 | ], 51 | ) 52 | return {elements[i]: colors[i % len(colors)] for i in range(len(elements))} 53 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/vector_selector.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | def add_vector_to_vector_selector_data( 5 | vector_selector_data: list, 6 | vector: str, 7 | description: Optional[str] = None, 8 | description_at_last_node: bool = False, 9 | ) -> None: 10 | nodes = vector.split(":") 11 | current_child_list = vector_selector_data 12 | for index, node in enumerate(nodes): 13 | found = False 14 | for child in current_child_list: 15 | if child["name"] == node: 16 | children = child["children"] 17 | current_child_list = children if children is not None else [] 18 | found = True 19 | break 20 | if not found: 21 | node_data: dict = { 22 | "name": node, 23 | "children": [] if index < len(nodes) - 1 else None, 24 | } 25 | if not description_at_last_node and description and index == 0: 26 | node_data["description"] = description 27 | elif description_at_last_node and description and (index == len(nodes) - 1): 28 | node_data["description"] = description 29 | 30 | current_child_list.append(node_data) 31 | 32 | children = current_child_list[-1]["children"] 33 | current_child_list = children if children is not None else [] 34 | 35 | 36 | def is_vector_name_in_vector_selector_data( 37 | name: str, vector_selector_data: list 38 | ) -> bool: 39 | nodes = name.split(":") 40 | current_child_list = vector_selector_data 41 | for node in nodes: 42 | found = False 43 | for child in current_child_list: 44 | if child["name"] == node: 45 | children = child["children"] 46 | current_child_list = children if children is not None else [] 47 | found = True 48 | break 49 | if not found: 50 | return False 51 | return found 52 | -------------------------------------------------------------------------------- /webviz_subsurface/_utils/webvizstore_functions.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | from pathlib import Path 4 | 5 | import pandas as pd 6 | from webviz_config.webviz_store import webvizstore 7 | 8 | 9 | @webvizstore 10 | def get_path(path: str) -> Path: 11 | return Path(path) 12 | 13 | 14 | @webvizstore 15 | def read_csv(csv_file: Path) -> pd.DataFrame: 16 | return pd.read_csv(csv_file) 17 | 18 | 19 | @webvizstore 20 | def find_files(folder: Path, suffix: str) -> io.BytesIO: 21 | return io.BytesIO( 22 | json.dumps( 23 | sorted([str(filename) for filename in folder.glob(f"*{suffix}")]) 24 | ).encode() 25 | ) 26 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """These are plugins relevant within subsurface workflows. Most of them 2 | rely on the setting `scratch_ensembles` within 3 | `shared_settings`. This setting connects ensemble names (user defined) 4 | with the paths to where the ensembles are stored, either absolute or 5 | relative to the location of the configuration file. 6 | I.e. you could have 7 | ```yaml 8 | title: Reek Webviz Demonstration 9 | shared_settings: 10 | scratch_ensembles: 11 | iter-0: /scratch/my_ensemble/realization-*/iter-0 12 | iter-1: /scratch/my_ensemble/realization-*/iter-1 13 | pages: 14 | - title: Front page 15 | content: 16 | - plugin: ReservoirSimulationTimeSeries 17 | ensembles: 18 | - iter-0 19 | - iter-1 20 | ``` 21 | """ 22 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import BhpQc 2 | from ._plugin_ids import PluginIds 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/_plugin_ids.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class PluginIds: # pylint: disable=too-few-public-methods 5 | class Stores(StrEnum): 6 | SELECTED_ENSEMBLE = "selected-ensemble" 7 | SELECTED_WELLS = "selected-wells" 8 | SELECTED_MAX_NUMBER_OF_WELLS = "selected-max-number-of-wells" 9 | SELECTED_SORT_BY = "selected-sort-by" 10 | SELECTED_ASCENDING_DESCENDING = "selected-ascending-descending" 11 | SELECTED_STATISTICS = "selected-statistics" 12 | 13 | class SharedSettings(StrEnum): 14 | FILTER = "filter" 15 | VIEW_SETTINGS = "view-settings" 16 | 17 | class BhpId(StrEnum): 18 | LINE_CHART = "line-chart" 19 | FAN_CHART = "fan-chart" 20 | BAR_CHART = "bar-chart" 21 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/shared_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._filter import Filter, ViewSettings 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from ._graph import Graph 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/view_elements/_graph.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from dash.development.base_component import Component 4 | from webviz_config.utils import StrEnum 5 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 6 | from webviz_core_components import Graph as WccGraph 7 | 8 | 9 | class Graph(ViewElementABC): 10 | class Ids(StrEnum): 11 | GRAPH = "graph" 12 | 13 | def __init__(self, height: str = "90vh") -> None: 14 | super().__init__() 15 | 16 | self.height = height 17 | 18 | def inner_layout(self) -> Type[Component]: 19 | return WccGraph( 20 | id=self.register_component_unique_id(Graph.Ids.GRAPH), 21 | style={"height": self.height, "min-height": "300px"}, 22 | ) 23 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_bhp_qc/views/__init__.py: -------------------------------------------------------------------------------- 1 | from ._bar_chart import BarView 2 | from ._fan_chart import FanView 3 | from ._line_chart import LineView 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import CO2Leakage 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_error.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | 4 | def error(error_message: str) -> html.Div: 5 | return html.Div(children=error_message, style={"color": "red"}) 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_types.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TypedDict 2 | 3 | 4 | class LegendData(TypedDict): 5 | bar_legendonly: Optional[List[str]] 6 | time_legendonly: Optional[List[str]] 7 | stats_legendonly: Optional[List[str]] 8 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_co2_leakage/_utilities/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Dict 3 | 4 | from fmu.ensemble import ScratchEnsemble 5 | 6 | 7 | def realization_paths(ens_path: str) -> Dict[int, Path]: 8 | scratch_ensemble = ScratchEnsemble("_", paths=ens_path).filter("OK") 9 | return {i: Path(r.runpath()) for i, r in scratch_ensemble.realizations.items()} 10 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional 3 | 4 | from webviz_subsurface._utils.enum_shim import StrEnum 5 | 6 | 7 | class StatisticsTabOption(StrEnum): 8 | PROBABILITY_PLOT = "probability-plot" 9 | BOX_PLOT = "box-plot" 10 | 11 | 12 | # pylint: disable=too-many-instance-attributes 13 | @dataclass(frozen=True) # NBNB-AS: Removed slots=True (python>=3.10) 14 | class ContainmentInfo: 15 | zone: Optional[str] 16 | region: Optional[str] 17 | zones: List[str] 18 | regions: Optional[List[str]] 19 | phase: Optional[str] 20 | containment: Optional[str] 21 | plume_group: Optional[str] 22 | color_choice: str 23 | mark_choice: str 24 | sorting: str 25 | phases: List[str] 26 | containments: List[str] 27 | plume_groups: List[str] 28 | use_stats: bool 29 | date_option: str 30 | statistics_tab_option: StatisticsTabOption 31 | box_show_points: str 32 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_utilities/fault_polygons_handler.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from webviz_subsurface._providers import ( 4 | EnsembleFaultPolygonsProviderFactory, 5 | FaultPolygonsServer, 6 | SimulatedFaultPolygonsAddress, 7 | ) 8 | 9 | 10 | class FaultPolygonsHandler: 11 | def __init__( 12 | self, 13 | server: FaultPolygonsServer, 14 | ensemble_path: str, 15 | map_surface_names_to_fault_polygons: Dict[str, str], 16 | fault_polygon_attribute: str, 17 | ) -> None: 18 | self._server = server 19 | polygon_provider_factory = EnsembleFaultPolygonsProviderFactory.instance() 20 | self._provider = ( 21 | polygon_provider_factory.create_from_ensemble_fault_polygons_files( 22 | ensemble_path 23 | ) 24 | ) 25 | server.add_provider(self._provider) 26 | self._map_surface_names_to_fault_polygons = map_surface_names_to_fault_polygons 27 | self._fault_polygon_attribute = fault_polygon_attribute 28 | 29 | def extract_fault_polygon_url( 30 | self, 31 | polygon_name: str, 32 | realization: List[int], 33 | ) -> Optional[str]: 34 | if polygon_name is None: 35 | return None 36 | if len(realization) == 0: 37 | return None 38 | # NB! This always returns the url corresponding to the first realization 39 | address = SimulatedFaultPolygonsAddress( 40 | attribute=self._fault_polygon_attribute, 41 | name=self._map_surface_names_to_fault_polygons.get( 42 | polygon_name, polygon_name 43 | ), 44 | realization=realization[0], 45 | ) 46 | return self._server.encode_partial_url( 47 | provider_id=self._provider.provider_id(), 48 | fault_polygons_address=address, 49 | ) 50 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/_utilities/polygon_handler.py: -------------------------------------------------------------------------------- 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 | ) 61 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_co2_leakage/views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_co2_leakage/views/mainview/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_co2_leakage/views/mainview/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import EXPERIMENTALGridViewerFMU 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/_layout_elements.py: -------------------------------------------------------------------------------- 1 | class ElementIds: 2 | # pylint: disable=too-few-public-methods 3 | ID = "grid-view" 4 | IJK_CROP_STORE = "ijk-filter-store" 5 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_subsurface._utils.enum_shim import StrEnum 2 | 3 | 4 | class PROPERTYTYPE(StrEnum): 5 | STATIC = "Static" 6 | DYNAMIC = "Dynamic" 7 | 8 | 9 | class GRIDDIRECTION(StrEnum): 10 | I = "I" 11 | J = "J" 12 | K = "K" 13 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_grid_viewer_fmu/views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._color_scale import ColorScale 2 | from ._data_selection import DataSettings 3 | from ._grid_filter import GridFilter 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/view_elements/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_grid_viewer_fmu/views/view_3d/view_elements/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import GroupTree 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class TreeModeOptions(StrEnum): 5 | STATISTICS = "statistics" 6 | SINGLE_REAL = "single_real" 7 | 8 | 9 | class StatOptions(StrEnum): 10 | MEAN = "mean" 11 | P10 = "p10" 12 | P50 = "p50" 13 | P90 = "p90" 14 | MAX = "max" 15 | MIN = "min" 16 | 17 | 18 | class NodeType(StrEnum): 19 | PROD = "prod" 20 | INJ = "inj" 21 | OTHER = "other" 22 | 23 | 24 | class DataType(StrEnum): 25 | OILRATE = "oilrate" 26 | GASRATE = "gasrate" 27 | WATERRATE = "waterrate" 28 | WATERINJRATE = "waterinjrate" 29 | GASINJRATE = "gasinjrate" 30 | PRESSURE = "pressure" 31 | BHP = "bhp" 32 | WMCTL = "wmctl" 33 | 34 | 35 | class EdgeOrNode(StrEnum): 36 | EDGE = "edge" 37 | NODE = "node" 38 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._ensemble_group_tree_data import EnsembleGroupTreeData 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_group_tree/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/_views/_group_tree_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import GroupTreeView 2 | from ._view_element import GroupTreeViewElement 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_group_tree/_views/_group_tree_view/_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class GroupTreeViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | COMPONENT = "component" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div( 15 | id=self.register_component_unique_id(GroupTreeViewElement.Ids.COMPONENT) 16 | ) 17 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_horizon_uncertainty_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from .horizon_uncertainty_viewer import HorizonUncertaintyViewer 2 | 3 | __all__ = ["HorizonUncertaintyViewer"] 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_horizon_uncertainty_viewer/_huv_table.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import List 3 | 4 | import pandas as pd 5 | 6 | 7 | class FilterTable: 8 | """Uses paths to csv files to render dataframes 9 | and to update them. 10 | * `target_points`: File path to targetpoints.csv 11 | * `well_points`: File path to wellpoints.csv 12 | """ 13 | 14 | def __init__( 15 | self, 16 | target_points: Path = None, 17 | well_points: Path = None, 18 | ): 19 | self.target_points = target_points 20 | self.well_points = well_points 21 | 22 | def get_targetpoints_df(self) -> pd.DataFrame: 23 | df = pd.read_csv(self.target_points) 24 | return df.round(2) 25 | 26 | def get_wellpoints_df(self) -> pd.DataFrame: 27 | df = pd.read_csv(self.well_points) 28 | return df.round(2) 29 | 30 | def update_wellpoints_df(self, column_list: List[str]) -> pd.DataFrame: 31 | df = pd.read_csv(self.well_points) 32 | sorted_list = [] 33 | for colm in df.keys().values: 34 | for col in column_list: 35 | if colm == col: 36 | sorted_list.append(col) 37 | return df[sorted_list].round(2) 38 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_line_plotter_fmu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_line_plotter_fmu/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_line_plotter_fmu/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .build_figure import build_figure 2 | from .update_figure_clientside import update_figure_clientside 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_line_plotter_fmu/controllers/update_figure_clientside.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Optional 2 | 3 | from dash import ALL, ClientsideFunction, Dash, Input, Output, callback_context 4 | from dash.exceptions import PreventUpdate 5 | from plotly.graph_objects import Figure 6 | 7 | 8 | def update_figure_clientside(app: Dash, get_uuid: Callable) -> None: 9 | @app.callback( 10 | Output( 11 | {"id": get_uuid("clientside"), "plotly_attribute": "update_layout"}, "data" 12 | ), 13 | Input({"id": get_uuid("plotly_layout"), "layout_attribute": ALL}, "value"), 14 | Input(get_uuid("plotly_uirevision"), "value"), 15 | ) 16 | def _update_layout(layout_attributes: Optional[List], uirevision: str) -> Dict: 17 | """Store plotly layout options from user selections in a dcc.Store""" 18 | if layout_attributes is None: 19 | return {} 20 | layout = {} 21 | for ctx in callback_context.inputs_list[0]: 22 | layout[ctx["id"]["layout_attribute"]] = ctx.get("value", None) 23 | layout["uirevision"] = str(uirevision) if uirevision else None 24 | return layout 25 | 26 | @app.callback( 27 | Output( 28 | {"id": get_uuid("clientside"), "plotly_attribute": "plotly_layout"}, "data" 29 | ), 30 | Input( 31 | {"id": get_uuid("clientside"), "plotly_attribute": "initial_layout"}, "data" 32 | ), 33 | Input( 34 | {"id": get_uuid("clientside"), "plotly_attribute": "update_layout"}, "data" 35 | ), 36 | ) 37 | def _update_plot_layout(initial_layout: dict, update_layout: dict) -> Dict: 38 | if initial_layout is None: 39 | raise PreventUpdate 40 | fig = Figure({"layout": initial_layout}) 41 | if update_layout is not None: 42 | fig.update_layout(update_layout) 43 | return fig["layout"] 44 | 45 | app.clientside_callback( 46 | ClientsideFunction(namespace="clientside", function_name="set_dcc_figure"), 47 | Output(get_uuid("graph"), "figure"), 48 | Input( 49 | {"id": get_uuid("clientside"), "plotly_attribute": "plotly_layout"}, "data" 50 | ), 51 | Input( 52 | {"id": get_uuid("clientside"), "plotly_attribute": "plotly_data"}, "data" 53 | ), 54 | ) 55 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_line_plotter_fmu/figures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_line_plotter_fmu/figures/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_line_plotter_fmu/views/plot_traces_view.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | 6 | 7 | def line_traces_view( 8 | get_uuid: Callable, 9 | ) -> html.Div: 10 | return wcc.Selectors( 11 | label="Plot data", 12 | children=[ 13 | wcc.RadioItems( 14 | id=get_uuid("mode"), 15 | options=[ 16 | {"value": "lines", "label": "Lines"}, 17 | {"value": "markers", "label": "Points"}, 18 | ], 19 | value="lines", 20 | ), 21 | wcc.Checklist( 22 | id=get_uuid("traces"), 23 | options=[ 24 | {"label": val, "value": val} 25 | for val in ["Realizations", "Mean", "P10/P90", "Low/High"] 26 | ], 27 | value=["Realizations"], 28 | labelStyle={"display": "block"}, 29 | ), 30 | wcc.Checklist( 31 | id=get_uuid("observations"), 32 | options=[{"label": val, "value": val} for val in ["Observations"]], 33 | value=["Observations"], 34 | labelStyle={"display": "block"}, 35 | ), 36 | wcc.Label(id=get_uuid("statistics_warning"), children=""), 37 | ], 38 | ) 39 | 40 | 41 | def highlight_realizations_view( 42 | get_uuid: Callable, realizations: List[int] 43 | ) -> html.Div: 44 | return wcc.Selectors( 45 | label="Highlight realizations", 46 | children=[ 47 | wcc.Select( 48 | id=get_uuid("highlight-realizations"), 49 | options=[{"label": val, "value": val} for val in realizations], 50 | value=None, 51 | ), 52 | html.Button( 53 | id=get_uuid("clear-highlight-realizations"), 54 | children="Clear", 55 | ), 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_map_viewer_fmu/__init__.py: -------------------------------------------------------------------------------- 1 | from .map_viewer_fmu import MapViewerFMU 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_map_viewer_fmu/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_subsurface._utils.enum_shim import StrEnum 2 | 3 | 4 | class LayerTypes(StrEnum): 5 | HILLSHADING = "Hillshading2DLayer" 6 | MAP3D = "MapLayer" 7 | COLORMAP = "ColormapLayer" 8 | WELL = "WellsLayer" 9 | WELLTOPSLAYER = "GeoJsonLayer" 10 | DRAWING = "DrawingLayer" 11 | FAULTPOLYGONS = "FaultPolygonsLayer" 12 | FIELD_OUTLINE = "GeoJsonLayer" 13 | GEOJSON = "GeoJsonLayer" 14 | 15 | 16 | class LayerNames(StrEnum): 17 | HILLSHADING = "Surface (hillshading)" 18 | MAP3D = "3D Map" 19 | COLORMAP = "Surface (color)" 20 | WELL = "Wells" 21 | WELLTOPSLAYER = "Well tops" 22 | DRAWING = "Drawings" 23 | FAULTPOLYGONS = "Fault polygons" 24 | GEOJSON = "GeoJsonLayer" 25 | 26 | 27 | class SurfaceMode(StrEnum): 28 | MEAN = "Mean" 29 | REALIZATION = "Single realization" 30 | OBSERVED = "Observed" 31 | STDDEV = "StdDev" 32 | MINIMUM = "Minimum" 33 | MAXIMUM = "Maximum" 34 | P10 = "P10" 35 | P90 = "P90" 36 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_map_viewer_fmu/_utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | import math 4 | from typing import Dict, List 5 | 6 | import geojson 7 | import xtgeo 8 | from PIL import Image, ImageDraw 9 | 10 | 11 | def round_to_significant(val: float, sig: int = 4) -> float: 12 | """Roud a number to a specified number of significant digits.""" 13 | if val == 0: 14 | return 0 15 | return round(val, sig - int(math.floor(math.log10(abs(val)))) - 1) 16 | 17 | 18 | def rgb_to_hex(color: List[int]) -> str: 19 | """Convert an RGB color to a hex string.""" 20 | return f"#{color[1]:02x}{color[2]:02x}{color[3]:02x}" 21 | 22 | 23 | def image_to_base64(img: Image) -> str: # type: ignore[valid-type] 24 | """Convert an image to a base64 string.""" 25 | buffer = io.BytesIO() 26 | img.save(buffer, format="PNG") # type: ignore[attr-defined] 27 | buffer.seek(0) 28 | return base64.b64encode(buffer.read()).decode("utf-8") 29 | 30 | 31 | def create_colormap_image_string( 32 | colors: List, width: int = 100, height: int = 20 33 | ) -> str: 34 | """Create a colormap image and return it as a base64 string.""" 35 | img = Image.new("RGB", (width, height)) 36 | draw = ImageDraw.Draw(img) 37 | 38 | for i, color in enumerate(colors): 39 | x_0 = int(i / len(colors) * width) 40 | x_1 = int((i + 1) / len(colors) * width) 41 | draw.rectangle([(x_0, 0), (x_1, height)], fill=rgb_to_hex(color)) 42 | 43 | return f"data:image/png;base64,{image_to_base64(img)}" 44 | 45 | 46 | def xtgeo_polygons_to_geojson(polygons: xtgeo.Polygons) -> Dict: 47 | feature_arr = [] 48 | for name, polygon in polygons.dataframe.groupby("POLY_ID"): 49 | coords = [list(zip(polygon.X_UTME, polygon.Y_UTMN))] 50 | feature = geojson.Feature( 51 | geometry=geojson.Polygon(coords), 52 | properties={ 53 | "name": f"id:{name}", 54 | "color": [200, 200, 200], 55 | }, 56 | ) 57 | feature_arr.append(feature) 58 | return geojson.FeatureCollection(features=feature_arr) 59 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import ParameterAnalysis 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class VisualizationType(StrEnum): 5 | HISTOGRAM = "histogram" 6 | DISTRIBUTION = "distribution" 7 | BOX = "box" 8 | STAT_TABLE = "stat-table" 9 | 10 | 11 | class LinePlotOptions(StrEnum): 12 | """ 13 | Type definition for visualization options in simulation time series 14 | """ 15 | 16 | REALIZATIONS = "realizations" 17 | STATISTICS = "statistics" 18 | STATISTICS_AND_REALIZATIONS = "statistics and realizations" 19 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._parameters_model import ParametersModel 2 | from ._provider_timesseries_datamodel import ProviderTimeSeriesDataModel 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_utils/_datetime_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | # DOCS: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior 4 | 5 | 6 | def from_str(date_str: str) -> datetime.datetime: 7 | return datetime.datetime.strptime(date_str, "%Y-%m-%d") 8 | 9 | 10 | def to_str(date: datetime.datetime) -> str: 11 | if date.hour != 0 or date.minute != 0 or date.second != 0 or date.microsecond != 0: 12 | raise ValueError( 13 | f"Invalid date resolution, expected no data for hour, minute, second" 14 | f" or microsecond for {str(date)}" 15 | ) 16 | return date.strftime("%Y-%m-%d") 17 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_parameter_analysis/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import ParameterDistributionView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._ensembles import ParamDistEnsembles 2 | from ._parameters import ParamDistParameters 3 | from ._visualization_type import ParamDistVisualizationType 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/_ensembles.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class ParamDistEnsembles(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | ENSEMBLE_A = "ensemble-a" 12 | ENSEMBLE_B = "ensemble-b" 13 | 14 | def __init__(self, ensembles: List[str]) -> None: 15 | super().__init__("Ensembles") 16 | if not ensembles: 17 | raise ValueError("List of ensembles can't be empty.") 18 | self._ensembles = ensembles 19 | 20 | def layout(self) -> List[Component]: 21 | return [ 22 | wcc.Dropdown( 23 | label="Ensemble A:", 24 | id=self.register_component_unique_id(self.Ids.ENSEMBLE_A), 25 | options=[{"label": ens, "value": ens} for ens in self._ensembles], 26 | multi=False, 27 | value=self._ensembles[0], 28 | clearable=False, 29 | ), 30 | wcc.Dropdown( 31 | label="Ensemble B:", 32 | id=self.register_component_unique_id(self.Ids.ENSEMBLE_B), 33 | options=[{"label": ens, "value": ens} for ens in self._ensembles], 34 | multi=False, 35 | value=self._ensembles[-1], 36 | clearable=False, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/_parameters.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class ParamDistParameters(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | SORT_BY = "sort-by" 12 | PARAMETERS = "parameters" 13 | 14 | def __init__(self, parameters: List[str]) -> None: 15 | super().__init__("Parameters") 16 | if not parameters: 17 | raise ValueError("List of parameters can't be empty.") 18 | self._parameters = parameters 19 | 20 | def layout(self) -> List[Component]: 21 | return [ 22 | wcc.RadioItems( 23 | label="Sort parameters by", 24 | id=self.register_component_unique_id(self.Ids.SORT_BY), 25 | className="block-options", 26 | options=[ 27 | {"label": "Name", "value": "Name"}, 28 | { 29 | "label": "Standard deviation", 30 | "value": "Stddev", 31 | }, 32 | { 33 | "label": "Average", 34 | "value": "Avg", 35 | }, 36 | ], 37 | value="Name", 38 | ), 39 | wcc.SelectWithLabel( 40 | label="Select parameters", 41 | id=self.register_component_unique_id(self.Ids.PARAMETERS), 42 | options=[{"label": i, "value": i} for i in self._parameters], 43 | value=[self._parameters[0]], 44 | multi=True, 45 | size=min(40, len(self._parameters)), 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/_visualization_type.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ...._types import VisualizationType 9 | 10 | 11 | class ParamDistVisualizationType(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | VISUALIZATION_TYPE = "visualization-type" 14 | 15 | def __init__(self) -> None: 16 | super().__init__("Visualization Type") 17 | 18 | def layout(self) -> List[Component]: 19 | return [ 20 | wcc.RadioItems( 21 | id=self.register_component_unique_id(self.Ids.VISUALIZATION_TYPE), 22 | options=[ 23 | {"label": "Histogram", "value": VisualizationType.HISTOGRAM}, 24 | { 25 | "label": "Distribution plots", 26 | "value": VisualizationType.DISTRIBUTION, 27 | }, 28 | {"label": "Box plots", "value": VisualizationType.BOX}, 29 | { 30 | "label": "Statistics table", 31 | "value": VisualizationType.STAT_TABLE, 32 | }, 33 | ], 34 | value=VisualizationType.HISTOGRAM, 35 | vertical=True, 36 | ) 37 | ] 38 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class ParamDistViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | CHART = "chart" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div(id=self.register_component_unique_id(self.Ids.CHART)) 15 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import ParameterResponseView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._options import ParamRespOptions 2 | from ._parameter_filter import ParamRespParameterFilter 3 | from ._selections import ParamRespSelections 4 | from ._vizualisation import ParamRespVizualisation 5 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_settings/_options.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash import dcc, html 5 | from dash.development.base_component import Component 6 | from webviz_config.utils import StrEnum 7 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 8 | 9 | 10 | class ParamRespOptions(SettingsGroupABC): 11 | class Ids(StrEnum): 12 | VECTOR_FILTER = "vector-filter" 13 | SUBMIT_VECTOR_FILTER = "submit-vector-filter" 14 | VECTOR_FILTER_STORE = "vector-filter-store" 15 | AUTO_COMPUTE_CORRELATIONS = "auto-compute-correlations" 16 | 17 | def __init__( 18 | self, 19 | ) -> None: 20 | super().__init__("Correlation Options") 21 | 22 | def layout(self) -> List[Component]: 23 | return [ 24 | html.Header("Vectors for parameter correlation"), 25 | dcc.Textarea( 26 | id=self.register_component_unique_id(self.Ids.VECTOR_FILTER), 27 | style={"width": "95%", "height": "60px", "resize": "none"}, 28 | placeholder="\nenter comma separated input\n* is used as wildcard", 29 | persistence=True, 30 | persistence_type="session", 31 | ), 32 | html.Button( 33 | "Submit", 34 | id=self.register_component_unique_id(self.Ids.SUBMIT_VECTOR_FILTER), 35 | style={ 36 | "marginBottom": "5px", 37 | "width": "100%", 38 | "height": "20px", 39 | "line-height": "20px", 40 | "background-color": "#E8E8E8", 41 | }, 42 | ), 43 | dcc.Store( 44 | id=self.register_component_unique_id(self.Ids.VECTOR_FILTER_STORE) 45 | ), 46 | wcc.Checklist( 47 | id=self.register_component_unique_id( 48 | self.Ids.AUTO_COMPUTE_CORRELATIONS 49 | ), 50 | options=[ 51 | {"label": "Calculate correlations", "value": "AutoCompute"}, 52 | ], 53 | value=["AutoCompute"], 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_settings/_parameter_filter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pandas as pd 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ......_components.parameter_filter import ParameterFilter 9 | 10 | 11 | class ParamRespParameterFilter(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | PARAM_FILTER = "param-filter" 14 | 15 | def __init__(self, parameter_df: pd.DataFrame, ensembles: List[str]) -> None: 16 | super().__init__("Parameter Filter") 17 | self._parameter_df = parameter_df 18 | self._ensembles = ensembles 19 | 20 | def layout(self) -> List[Component]: 21 | return ParameterFilter( 22 | uuid=self.register_component_unique_id(self.Ids.PARAM_FILTER), 23 | dframe=self._parameter_df[ 24 | self._parameter_df["ENSEMBLE"].isin(self._ensembles) 25 | ].copy(), 26 | reset_on_ensemble_update=True, 27 | display_header=False, 28 | include_sens_filter=True, 29 | ).layout 30 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._color_figure import color_figure 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_utils/_color_figure.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import plotly.express as px 4 | import plotly.graph_objs as go 5 | 6 | 7 | def color_figure( 8 | colors: list, 9 | bargap: float = None, 10 | height: float = None, 11 | ) -> go.Figure: 12 | """ 13 | Create bar chart with colors, can e.g. be used as a color selector 14 | by retrieving clickdata. 15 | The color argument is a list of items where the individual items 16 | are either a name of a built-in plotly colorscale, or a list of colors. 17 | """ 18 | color_lists = [] 19 | for item in colors: 20 | if isinstance(item, list) and item: 21 | color_lists.append(item) 22 | if isinstance(item, str): 23 | color_lists.append(get_px_colors(item)) 24 | 25 | return go.Figure( 26 | data=[ 27 | go.Bar( 28 | orientation="h", 29 | y=[str(idx)] * len(clist), 30 | x=[1] * len(clist), 31 | customdata=list(range(len(clist))), 32 | marker={"color": clist}, 33 | hovertemplate="%{marker.color}", 34 | ) 35 | for idx, clist in enumerate(reversed(color_lists)) 36 | ], 37 | layout={ 38 | "title": None, 39 | "barmode": "stack", 40 | "barnorm": "fraction", 41 | "bargap": bargap if bargap is not None else 0.5, 42 | "showlegend": False, 43 | "xaxis": { 44 | "range": [-0.02, 1.02], 45 | "showticklabels": False, 46 | "showgrid": False, 47 | }, 48 | "yaxis_showticklabels": False, 49 | "height": height if height is not None else 20 * len(color_lists), 50 | "margin": {"l": 0, "r": 0, "t": 0, "b": 0}, 51 | "paper_bgcolor": "rgba(0,0,0,0)", 52 | "plot_bgcolor": "rgba(0,0,0,0)", 53 | }, 54 | ) 55 | 56 | 57 | def get_px_colors(px_cscale: str) -> List[str]: 58 | for cmodule in [ 59 | px.colors.diverging, 60 | px.colors.sequential, 61 | px.colors.cyclical, 62 | px.colors.qualitative, 63 | ]: 64 | if px_cscale in cmodule.__dict__: 65 | return cmodule.__dict__[px_cscale] 66 | return [] 67 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_response_view/_view_element.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from dash import html 3 | from webviz_config.utils import StrEnum 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | 6 | 7 | class ParamRespViewElement(ViewElementABC): 8 | class Ids(StrEnum): 9 | GRAPH = "graph" 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def inner_layout(self) -> html.Div: 15 | return html.Div( 16 | children=wcc.Graph( 17 | id=self.register_component_unique_id(self.Ids.GRAPH), 18 | style={"height": "43.5vh"}, 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import ParameterCorrelation, get_parameters 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/_error.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | 4 | def error(error_message: str) -> html.Div: 5 | return html.Div(children=error_message, style={"color": "red"}) 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/_plugin_ids.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | class PluginIds: 3 | class ParaCorrGroups: 4 | GROUPNAME = "parameter-correlation-group" 5 | PARACORR = "paracorr" 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .parameter_plot import ParameterPlot 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/views/parameter_plot/__init__.py: -------------------------------------------------------------------------------- 1 | from ._parameter_plot import ParameterPlot 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_parameter_correlation/views/parameter_plot/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._parameter_settings import ParameterSettings 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import ProdMisfit 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/_plugin_ids.py: -------------------------------------------------------------------------------- 1 | class PluginIds: 2 | # pylint: disable=too-few-public-methods 3 | class Stores: 4 | # pylint: disable=too-few-public-methods 5 | SELECTED_ENSEMBLES = "selected-ensembles" 6 | SELECTED_DATES = "selected-dates" 7 | SELECTED_PHASE = "selected-phase" 8 | SELECTED_WELLS = "selected-wells" 9 | SELECTED_COMBINE_WELLS_COLLECTION = "selected-combine-wells-collection" 10 | SELECTED_WELL_COLLECTIONS = "selected-well-collections" 11 | SELECTED_REALIZATIONS = "selected-realizations" 12 | 13 | SELECTED_FIG_LAYOUT_HEIGHT = "selected-fig-layout-height" 14 | 15 | class SharedSettings: 16 | # pylint: disable=too-few-public-methods 17 | FILTER = "filter" 18 | 19 | class MisfitViews: 20 | # pylint: disable=too-few-public-methods 21 | GROUP_NAME = "Misfit analysis" 22 | 23 | PRODUCTION_MISFIT_PER_REAL = "production-misfit-per-real" 24 | WELL_PRODUCTION_COVERAGE = "well-production-coverage" 25 | WELL_PRODUCTION_HEATMAP = "well-production-heatmap" 26 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/shared_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._filter import Filter 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_prod_misfit/utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from ._graph import Graph 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/view_elements/_graph.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from dash.development.base_component import Component 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | from webviz_core_components import Graph as WccGraph 6 | 7 | 8 | class Graph(ViewElementABC): 9 | class Ids: 10 | # pylint: disable=too-few-public-methods 11 | GRAPH = "graph" 12 | 13 | def __init__(self, height: str = "65vh") -> None: 14 | super().__init__() 15 | 16 | self.height = height 17 | 18 | def inner_layout(self) -> Type[Component]: 19 | return WccGraph( 20 | id=self.register_component_unique_id(Graph.Ids.GRAPH), 21 | style={"height": self.height, "min-height": "300px"}, 22 | ) 23 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/views/__init__.py: -------------------------------------------------------------------------------- 1 | from ._production_misfit_per_real import ( 2 | MisfitOptions, 3 | MisfitPerRealView, 4 | PlotSettingsMisfit, 5 | ) 6 | from ._well_production_coverage import PlotSettingsCoverage, ProdCoverageView 7 | from ._well_production_heatmap import PlotSettingsHeatmap, ProdHeatmapView 8 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_prod_misfit/views/_view_functions.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | def _get_well_names_combined( 5 | well_collections: Dict[str, List[str]], 6 | selected_collection_names: list, 7 | selected_wells: list, 8 | combine_type: str = "intersection", 9 | ) -> List[str]: 10 | """Return union or intersection of well list and well collection lists.""" 11 | 12 | selected_collection_wells = [] 13 | for collection_name in selected_collection_names: 14 | selected_collection_wells.extend(well_collections[collection_name]) 15 | selected_collection_wells = list(set(selected_collection_wells)) 16 | if combine_type == "intersection": 17 | # find intersection of selector wells and selector well collections 18 | well_names_combined = [ 19 | well for well in selected_wells if well in selected_collection_wells 20 | ] 21 | else: 22 | # find union of selector wells and selector well collections 23 | well_names_combined = list(selected_collection_wells) 24 | well_names_combined.extend(selected_wells) 25 | well_names_combined = list(set(well_names_combined)) 26 | 27 | return well_names_combined 28 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/__init__.py: -------------------------------------------------------------------------------- 1 | from .property_statistics import PropertyStatistics 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .property_delta_controller import property_delta_controller 2 | from .property_qc_controller import property_qc_controller 3 | from .property_response_controller import property_response_controller 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/data_loaders/__init__.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import pandas as pd 4 | from webviz_config.common_cache import CACHE 5 | from webviz_config.webviz_store import webvizstore 6 | 7 | 8 | @CACHE.memoize() 9 | @webvizstore 10 | def read_csv(csv_file: pathlib.Path) -> pd.DataFrame: 11 | return pd.read_csv(csv_file) 12 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/figures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_property_statistics/figures/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/figures/correlation_figure.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pandas as pd 4 | 5 | 6 | class CorrelationFigure: 7 | def __init__(self, series: pd.Series, n_rows: int, title: str): 8 | self.series = series.tail(n=n_rows) 9 | self.title = title 10 | self._data = self.initial_data 11 | 12 | @property 13 | def data(self) -> List[dict]: 14 | return self._data 15 | 16 | @property 17 | def figure(self) -> dict: 18 | return {"data": self.data, "layout": self.layout} 19 | 20 | @property 21 | def initial_data(self) -> List[dict]: 22 | return [ 23 | { 24 | "x": self.series.values, 25 | "y": self.series.index, 26 | "orientation": "h", 27 | "type": "bar", 28 | "marker": {}, 29 | } 30 | ] 31 | 32 | @property 33 | def layout(self) -> dict: 34 | return { 35 | "barmode": "relative", 36 | "xaxis": {"range": [-1, 1]}, 37 | "yaxis": {"automargin": True}, 38 | "automargin": True, 39 | } 40 | 41 | @property 42 | def first_y_value(self) -> str: 43 | return self.data[0]["y"][-1] 44 | 45 | def set_bar_colors(self, selected_bar: str) -> None: 46 | self._data[0]["marker"]["color"] = [ 47 | "grey" if _bar != selected_bar else "red" for _bar in self.data[0]["y"] 48 | ] 49 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_timeseries_datamodel import ProviderTimeSeriesDataModel 2 | from .property_statistics_model import PropertyStatisticsModel 3 | from .simulation_timeseries_model import SimulationTimeSeriesModel 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_property_statistics/utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .main_view import main_view 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_property_statistics/views/main_view.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Optional, Union 2 | 3 | import webviz_core_components as wcc 4 | 5 | from ..models import ( 6 | PropertyStatisticsModel, 7 | ProviderTimeSeriesDataModel, 8 | SimulationTimeSeriesModel, 9 | ) 10 | from .property_delta_view import property_delta_view 11 | from .property_qc_view import property_qc_view 12 | from .property_response_view import property_response_view 13 | 14 | 15 | def main_view( 16 | get_uuid: Callable, 17 | property_model: PropertyStatisticsModel, 18 | vector_model: Optional[ 19 | Union[SimulationTimeSeriesModel, ProviderTimeSeriesDataModel] 20 | ], 21 | surface_folders: Optional[Dict] = None, 22 | ) -> wcc.Tabs: 23 | tabs = [ 24 | wcc.Tab( 25 | label="Property QC", 26 | children=property_qc_view(get_uuid=get_uuid, property_model=property_model), 27 | ) 28 | ] 29 | if len(property_model.ensembles) > 1: 30 | tabs.append( 31 | wcc.Tab( 32 | label="AHM impact on property", 33 | children=property_delta_view( 34 | get_uuid=get_uuid, 35 | property_model=property_model, 36 | surface_folders=surface_folders, 37 | ), 38 | ) 39 | ) 40 | if vector_model is not None: 41 | tabs.append( 42 | wcc.Tab( 43 | label="Property impact on simulation profiles", 44 | children=property_response_view( 45 | get_uuid=get_uuid, 46 | property_model=property_model, 47 | vector_model=vector_model, 48 | surface_folders=surface_folders, 49 | ), 50 | ), 51 | ) 52 | 53 | return wcc.Tabs(style={"width": "100%"}, children=tabs) 54 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import PvtPlot 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_pvt_plot/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/_views/_pvt/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import PvtView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/_views/_pvt/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._data_settings import ColorBy, DataSettings 2 | from ._view_settings import ViewSettings 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/_views/_pvt/_settings/_view_settings.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class ViewSettings(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | SHOW_PLOTS = "show-plots" 12 | 13 | def __init__(self) -> None: 14 | super().__init__("Show Plots") 15 | 16 | @staticmethod 17 | def plot_visibility_options(phase: str = "") -> Dict[str, str]: 18 | options = { 19 | "fvf": "Formation Volume Factor", 20 | "viscosity": "Viscosity", 21 | "density": "Density", 22 | "ratio": "Fluid Ratio", 23 | } 24 | if phase == "OIL": 25 | options["ratio"] = "Gas/Oil Ratio (Rs) at Psat" 26 | if phase == "GAS": 27 | options["ratio"] = "Vaporized Oil Ratio (Rv) at Psat" 28 | if phase == "WATER": 29 | options.pop("ratio") 30 | return options 31 | 32 | def layout(self) -> List[Component]: 33 | component_id = self.register_component_unique_id(ViewSettings.Ids.SHOW_PLOTS) 34 | return [ 35 | wcc.Checklist( 36 | id={"id": component_id, "plot": plot_value}, 37 | options=[{"label": plot_label, "value": plot_value}], 38 | value=[plot_value], 39 | persistence=False, 40 | ) 41 | for plot_value, plot_label in self.plot_visibility_options().items() 42 | ] 43 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_pvt_plot/_views/_pvt/_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_pvt_plot/_views/_pvt/_utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import RftPlotter 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_reusable_settings.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class FilterLayout(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | FILTER_WELLS = "filter-wells" 12 | FILTER_ZONES = "filter-zones" 13 | FILTER_DATES = "filter-dates" 14 | 15 | def __init__(self, wells: List[str], zones: List[str], dates: List[str]) -> None: 16 | super().__init__("Filters") 17 | self._wells = wells 18 | self._zones = zones 19 | self._dates = dates 20 | 21 | def layout(self) -> List[Component]: 22 | return [ 23 | wcc.SelectWithLabel( 24 | label="Wells", 25 | size=min(10, len(self._wells)), 26 | id=self.register_component_unique_id(self.Ids.FILTER_WELLS), 27 | options=[{"label": name, "value": name} for name in self._wells], 28 | value=self._wells, 29 | multi=True, 30 | ), 31 | wcc.SelectWithLabel( 32 | label="Zones", 33 | size=min(10, len(self._zones)), 34 | id=self.register_component_unique_id(self.Ids.FILTER_ZONES), 35 | options=[{"label": name, "value": name} for name in self._zones], 36 | value=self._zones, 37 | multi=True, 38 | ), 39 | wcc.SelectWithLabel( 40 | label="Dates", 41 | size=min(10, len(self._dates)), 42 | id=self.register_component_unique_id(self.Ids.FILTER_DATES), 43 | options=[{"label": name, "value": name} for name in self._dates], 44 | value=self._dates, 45 | multi=True, 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_reusable_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class GeneralViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | CHART = "chart" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div(id=self.register_component_unique_id(self.Ids.CHART)) 15 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class LineType(StrEnum): 5 | REALIZATION = "realization" 6 | FANCHART = "fanchart" 7 | 8 | 9 | class DepthType(StrEnum): 10 | TVD = "TVD" 11 | MD = "MD" 12 | 13 | 14 | class ColorAndSizeByType(StrEnum): 15 | MISFIT = "ABSDIFF" 16 | STDDEV = "STDDEV" 17 | YEAR = "YEAR" 18 | 19 | 20 | class CorrType(StrEnum): 21 | SIM_VS_PARAM = "sim_vs_param" 22 | PARAM_VS_SIM = "param_vs_sim" 23 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._formation_figure import FormationFigure 2 | from ._rft_plotter_data_model import ( 3 | RftPlotterDataModel, 4 | correlate, 5 | filter_frame, 6 | interpolate_depth, 7 | ) 8 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_rft_plotter/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_map_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import MapView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_map_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._formation_plot_settings import FormationPlotSettings 2 | from ._map_settings import MapSettings 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_map_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._map_figure import MapFigure 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_misfit_per_real_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import MisfitPerRealView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_misfit_per_real_view/_settings.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class Selections(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | ENSEMBLES = "ensembles" 12 | 13 | def __init__(self, ensembles: List[str]) -> None: 14 | super().__init__("Selections") 15 | self._ensembles = ensembles 16 | 17 | def layout(self) -> List[Component]: 18 | return [ 19 | wcc.Dropdown( 20 | label="Ensembles", 21 | id=self.register_component_unique_id(self.Ids.ENSEMBLES), 22 | options=[{"label": ens, "value": ens} for ens in self._ensembles], 23 | value=[self._ensembles[0] if self._ensembles else None], 24 | clearable=False, 25 | multi=True, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_misfit_per_real_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._misfit_per_real_figure import update_misfit_per_real_plot 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_parameter_response_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import ParameterResponseView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_parameter_response_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._options import Options 2 | from ._parameter_filter import ParameterFilterSettings 3 | from ._selections import Selections 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_parameter_response_view/_settings/_options.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ...._types import CorrType, DepthType 9 | 10 | 11 | class Options(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | CORRTYPE = "corrtype" 14 | DEPTHTYPE = "depthtype" 15 | 16 | def __init__(self) -> None: 17 | super().__init__("Options") 18 | 19 | def layout(self) -> List[Component]: 20 | return [ 21 | wcc.RadioItems( 22 | label="Correlation options", 23 | id=self.register_component_unique_id(self.Ids.CORRTYPE), 24 | options=[ 25 | { 26 | "label": "Simulated vs parameters", 27 | "value": CorrType.SIM_VS_PARAM, 28 | }, 29 | { 30 | "label": "Parameter vs simulated", 31 | "value": CorrType.PARAM_VS_SIM, 32 | }, 33 | ], 34 | value="sim_vs_param", 35 | ), 36 | wcc.RadioItems( 37 | label="Depth option", 38 | id=self.register_component_unique_id(self.Ids.DEPTHTYPE), 39 | options=[ 40 | { 41 | "label": "TVD", 42 | "value": DepthType.TVD, 43 | }, 44 | { 45 | "label": "MD", 46 | "value": DepthType.MD, 47 | }, 48 | ], 49 | value=DepthType.TVD, 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_parameter_response_view/_settings/_parameter_filter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pandas as pd 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ......_components.parameter_filter import ParameterFilter 9 | 10 | 11 | class ParameterFilterSettings(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | PARAM_FILTER = "param-filter" 14 | 15 | def __init__(self, parameter_df: pd.DataFrame, ensembles: List[str]) -> None: 16 | super().__init__("Parameter Filter") 17 | self._parameter_df = parameter_df 18 | self._ensembles = ensembles 19 | 20 | def layout(self) -> List[Component]: 21 | return ParameterFilter( 22 | uuid=self.register_component_unique_id(self.Ids.PARAM_FILTER), 23 | dframe=self._parameter_df[ 24 | self._parameter_df["ENSEMBLE"].isin(self._ensembles) 25 | ].copy(), 26 | reset_on_ensemble_update=True, 27 | display_header=False, 28 | include_sens_filter=True, 29 | ).layout 30 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_qc_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import QCView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_qc_view/_table_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import dash_table, html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class TableViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | TABLE = "table" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div( 15 | children=[ 16 | html.Div( 17 | children=dash_table.DataTable( 18 | id=self.register_component_unique_id(self.Ids.TABLE), 19 | sort_action="native", 20 | sort_mode="multi", 21 | filter_action="native", 22 | style_as_list_view=True, 23 | style_table={ 24 | "height": "84vh", 25 | "overflowY": "auto", 26 | }, 27 | style_cell={ 28 | "whiteSpace": "normal", 29 | "height": "auto", 30 | "textAlign": "left", 31 | "width": "auto", 32 | }, 33 | ), 34 | ), 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import SimVsObsView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._ensembles import Ensembles 2 | from ._plot_type import PlotType, PlotTypeSettings 3 | from ._size_color_settings import SizeColorSettings 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_settings/_ensembles.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class Ensembles(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | ENSEMBLES = "ensembles" 12 | 13 | def __init__(self, ensembles: List[str]) -> None: 14 | super().__init__("Ensembles") 15 | self._ensembles = ensembles 16 | 17 | def layout(self) -> List[Component]: 18 | return [ 19 | wcc.Dropdown( 20 | id=self.register_component_unique_id(self.Ids.ENSEMBLES), 21 | options=[{"label": ens, "value": ens} for ens in self._ensembles], 22 | value=[self._ensembles[0] if self._ensembles else None], 23 | clearable=False, 24 | multi=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_settings/_plot_type.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class PlotType(StrEnum): 10 | CROSSPLOT = "crossplot" 11 | ERROR_BOXPLOT = "error-boxplot" 12 | 13 | 14 | class PlotTypeSettings(SettingsGroupABC): 15 | class Ids(StrEnum): 16 | PLOT_TYPE = "plot-type" 17 | 18 | def __init__(self) -> None: 19 | super().__init__("Plot Type") 20 | 21 | def layout(self) -> List[Component]: 22 | return [ 23 | wcc.RadioItems( 24 | id=self.register_component_unique_id(self.Ids.PLOT_TYPE), 25 | options=[ 26 | { 27 | "label": "CrossPlot", 28 | "value": PlotType.CROSSPLOT, 29 | }, 30 | { 31 | "label": "Error BoxPlot", 32 | "value": PlotType.ERROR_BOXPLOT, 33 | }, 34 | ], 35 | value=PlotType.CROSSPLOT, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_settings/_size_color_settings.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ...._types import ColorAndSizeByType 9 | 10 | 11 | class SizeColorSettings(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | CROSSPLOT_COLOR_BY = "crossplot-color-by" 14 | CROSSPLOT_SIZE_BY = "crossplot-size-by" 15 | 16 | def __init__(self) -> None: 17 | super().__init__("Crossplot options") 18 | 19 | def layout(self) -> List[Component]: 20 | return [ 21 | wcc.Dropdown( 22 | label="Color by", 23 | id=self.register_component_unique_id(self.Ids.CROSSPLOT_COLOR_BY), 24 | options=[ 25 | { 26 | "label": "Misfit", 27 | "value": ColorAndSizeByType.MISFIT, 28 | }, 29 | { 30 | "label": "Standard Deviation", 31 | "value": ColorAndSizeByType.STDDEV, 32 | }, 33 | ], 34 | value=ColorAndSizeByType.STDDEV, 35 | clearable=False, 36 | ), 37 | wcc.Dropdown( 38 | label="Size by", 39 | id=self.register_component_unique_id(self.Ids.CROSSPLOT_SIZE_BY), 40 | options=[ 41 | { 42 | "label": "Standard Deviation", 43 | "value": ColorAndSizeByType.STDDEV, 44 | }, 45 | { 46 | "label": "Misfit", 47 | "value": ColorAndSizeByType.MISFIT, 48 | }, 49 | ], 50 | value=ColorAndSizeByType.MISFIT, 51 | clearable=False, 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._crossplot_figure import update_crossplot 2 | from ._errorplot_figure import update_errorplot 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_rft_plotter/_views/_sim_vs_obs_view/_utils/_errorplot_figure.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | import pandas as pd 4 | import webviz_core_components as wcc 5 | 6 | 7 | def update_errorplot(df: pd.DataFrame, enscolors: Dict[str, str]) -> wcc.Graph: 8 | df["RFT_NAME"] = df.agg( 9 | lambda x: f"{x['WELL']} {int(x['YEAR'])} {x['ZONE']} ({int(x['MD'])} MD)", 10 | axis=1, 11 | ) 12 | df["DIFFMEAN"] = df.groupby(["WELL", "DATE", "ZONE", "MD", "ENSEMBLE"])[ 13 | "ABSDIFF" 14 | ].transform("median") 15 | traces = [] 16 | for i, (ensemble, ensdf) in enumerate(df.groupby("ENSEMBLE")): 17 | if i == 0: 18 | ensdf = ensdf.sort_values(by=["DIFFMEAN"]) 19 | traces.append( 20 | { 21 | "x": ensdf["DIFF"], 22 | "y": ensdf["RFT_NAME"], 23 | "name": ensemble, 24 | "type": "box", 25 | "orientation": "h", 26 | "offsetgroup": i, 27 | "marker": {"color": enscolors[ensemble]}, 28 | } 29 | ) 30 | layout = { 31 | "margin": {"l": 250, "r": 0, "b": 50, "t": 100}, 32 | "height": 750, 33 | "legend": {"orientation": "h"}, 34 | "boxmode": "group", 35 | "xaxis": {"title": "Difference in bar"}, 36 | } 37 | 38 | return wcc.Graph( 39 | style={"height": "84vh"}, figure={"data": traces, "layout": layout} 40 | ) 41 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import SimulationTimeSeries 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_simulation_time_series/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import SubplotView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_property_serialization/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensemble_subplot_builder import EnsembleSubplotBuilder 2 | from .graph_figure_builder_base import GraphFigureBuilderBase 3 | from .vector_subplot_builder import VectorSubplotBuilder 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._ensembles import EnsemblesSettings 2 | from ._filter_realization import FilterRealizationSettings 3 | from ._group_by import GroupBySettings 4 | from ._resampling_frequency import ResamplingFrequencySettings 5 | from ._time_series import TimeSeriesSettings 6 | from ._visualization import VisualizationSettings 7 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_settings/_group_by.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from .._types import SubplotGroupByOptions 9 | 10 | 11 | class GroupBySettings(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | SUBPLOT_OWNER_OPTIONS_RADIO_ITEMS = "subplot-owner-options-radio-items" 14 | 15 | def __init__(self) -> None: 16 | super().__init__("Group by") 17 | 18 | def layout(self) -> List[Component]: 19 | return [ 20 | wcc.RadioItems( 21 | id=self.register_component_unique_id( 22 | GroupBySettings.Ids.SUBPLOT_OWNER_OPTIONS_RADIO_ITEMS 23 | ), 24 | options=[ 25 | { 26 | "label": "Time Series", 27 | "value": SubplotGroupByOptions.VECTOR, 28 | }, 29 | { 30 | "label": "Ensemble", 31 | "value": SubplotGroupByOptions.ENSEMBLE, 32 | }, 33 | ], 34 | value=SubplotGroupByOptions.VECTOR, 35 | ) 36 | ] 37 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_types/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | DeltaEnsemble, 3 | FanchartOptions, 4 | StatisticsFromOptions, 5 | StatisticsOptions, 6 | SubplotGroupByOptions, 7 | TraceOptions, 8 | VisualizationOptions, 9 | ) 10 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_types/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | 3 | from webviz_config.utils import StrEnum 4 | 5 | 6 | class DeltaEnsemble(TypedDict): 7 | """Definition of delta ensemble 8 | 9 | Pair of names representing a delta ensemble: A-B 10 | """ 11 | 12 | ensemble_a: str 13 | ensemble_b: str 14 | 15 | 16 | class FanchartOptions(StrEnum): 17 | """ 18 | Type definition for statistical options for fanchart 19 | """ 20 | 21 | MEAN = "Mean" # Mean value 22 | MIN_MAX = "Min/Max" # Minimum and maximum pair 23 | P10_P90 = "P10/P90" # P10 and P90 pair 24 | 25 | 26 | class StatisticsFromOptions(StrEnum): 27 | """ 28 | Type definition of options for what to generate statistics from in simulation time series 29 | """ 30 | 31 | ALL_REALIZATIONS = "all_realizations" 32 | SELECTED_REALIZATIONS = "selected_realizations" 33 | 34 | 35 | class StatisticsOptions(StrEnum): 36 | """ 37 | Type definition of statistics to include in plot in simulation time series 38 | """ 39 | 40 | MEAN = "Mean" 41 | MIN = "Min" 42 | MAX = "Max" 43 | P10 = "P10" 44 | P90 = "P90" 45 | P50 = "P50" 46 | 47 | 48 | class SubplotGroupByOptions(StrEnum): 49 | """ 50 | Type definition of options for subplots "group by" in graph for simulation time series 51 | """ 52 | 53 | VECTOR = "vector" 54 | ENSEMBLE = "ensemble" 55 | 56 | 57 | class TraceOptions(StrEnum): 58 | """ 59 | Type definition for trace options in simulation time series 60 | """ 61 | 62 | HISTORY = "history" 63 | OBSERVATIONS = "observations" 64 | 65 | 66 | class VisualizationOptions(StrEnum): 67 | """ 68 | Type definition for visualization options in simulation time series 69 | """ 70 | 71 | REALIZATIONS = "realizations" 72 | STATISTICS = "statistics" 73 | FANCHART = "fanchart" 74 | STATISTICS_AND_REALIZATIONS = "statistics and realizations" 75 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .derived_vectors_accessor import ( 2 | DerivedDeltaEnsembleVectorsAccessorImpl, 3 | DerivedEnsembleVectorsAccessorImpl, 4 | DerivedVectorsAccessor, 5 | ) 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_utils/datetime_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | # DOCS: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior 4 | 5 | 6 | def from_str(date_str: str) -> datetime.datetime: 7 | return datetime.datetime.strptime(date_str, "%Y-%m-%d") 8 | 9 | 10 | def to_str(date: datetime.datetime) -> str: 11 | if date.hour != 0 or date.minute != 0 or date.second != 0 or date.microsecond != 0: 12 | raise ValueError( 13 | f"Invalid date resolution, expected no data for hour, minute, second" 14 | f" or microsecond for {str(date)}" 15 | ) 16 | return date.strftime("%Y-%m-%d") 17 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_utils/derived_vectors_accessor/__init__.py: -------------------------------------------------------------------------------- 1 | from .derived_delta_ensemble_vectors_accessor_impl import ( 2 | DerivedDeltaEnsembleVectorsAccessorImpl, 3 | ) 4 | from .derived_ensemble_vectors_accessor_impl import DerivedEnsembleVectorsAccessorImpl 5 | from .derived_vectors_accessor import DerivedVectorsAccessor 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_utils/derived_vectors_accessor/derived_vectors_accessor.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import List, Optional, Sequence 3 | 4 | import pandas as pd 5 | 6 | 7 | class DerivedVectorsAccessor: 8 | def __init__(self, accessor_realizations: List[int]) -> None: 9 | self._accessor_realizations: List[int] = accessor_realizations 10 | 11 | @abc.abstractmethod 12 | def has_provider_vectors(self) -> bool: 13 | ... 14 | 15 | @abc.abstractmethod 16 | def has_per_interval_and_per_day_vectors(self) -> bool: 17 | ... 18 | 19 | @abc.abstractmethod 20 | def has_vector_calculator_expressions(self) -> bool: 21 | ... 22 | 23 | @abc.abstractmethod 24 | def get_provider_vectors_df( 25 | self, realizations: Optional[Sequence[int]] = None 26 | ) -> pd.DataFrame: 27 | ... 28 | 29 | @abc.abstractmethod 30 | def create_per_interval_and_per_day_vectors_df( 31 | self, 32 | realizations: Optional[Sequence[int]] = None, 33 | ) -> pd.DataFrame: 34 | ... 35 | 36 | @abc.abstractmethod 37 | def create_calculated_vectors_df( 38 | self, realizations: Optional[Sequence[int]] = None 39 | ) -> pd.DataFrame: 40 | ... 41 | 42 | def create_valid_realizations_query( 43 | self, selected_realizations: List[int] 44 | ) -> Optional[List[int]]: 45 | """Create realizations query for accessor based on selected realizations. 46 | 47 | `Returns:` 48 | - None - If all realizations for accessor is selected, i.e. the query is non-filtering 49 | - List[int] - List of realization numbers existing for the accessor - empty list 50 | is returned if no realizations exist. 51 | """ 52 | if set(self._accessor_realizations).issubset(set(selected_realizations)): 53 | return None 54 | return [ 55 | realization 56 | for realization in selected_realizations 57 | if realization in self._accessor_realizations 58 | ] 59 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_utils/trace_line_shape.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from webviz_subsurface._providers import VectorMetadata 4 | 5 | from .from_timeseries_cumulatives import is_per_interval_or_per_day_vector 6 | 7 | 8 | def get_simulation_line_shape( 9 | line_shape_fallback: str, 10 | vector: str, 11 | vector_metadata: Optional[VectorMetadata] = None, 12 | ) -> str: 13 | """Get simulation time series line shape based on vector metadata""" 14 | if is_per_interval_or_per_day_vector(vector): 15 | # These custom calculated vectors are valid forwards in time. 16 | return "hv" 17 | 18 | if vector_metadata is None: 19 | return line_shape_fallback 20 | if vector_metadata.is_rate: 21 | # Eclipse rate vectors are valid backwards in time. 22 | return "vh" 23 | if vector_metadata.is_total: 24 | return "linear" 25 | return line_shape_fallback 26 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from ._subplot_graph import SubplotGraph 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series/_views/_subplot_view/_view_elements/_subplot_graph.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class SubplotGraph(ViewElementABC): 7 | """View element for subplot graph""" 8 | 9 | class Ids(StrEnum): 10 | GRAPH = "graph" 11 | 12 | def __init__(self, height: str = "86vh") -> None: 13 | super().__init__() 14 | self.height = height 15 | 16 | def inner_layout(self) -> wcc.Graph: 17 | return wcc.Graph( 18 | style={"display": "block", "height": self.height}, 19 | id=self.register_component_unique_id(SubplotGraph.Ids.GRAPH), 20 | ) 21 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import SimulationTimeSeriesOneByOne 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class LineType(StrEnum): 5 | REALIZATION = "realizations" 6 | STATISTICS = "statistics" 7 | 8 | 9 | class ScaleType(StrEnum): 10 | PERCENTAGE = "Percentage" 11 | ABSOLUTE = "Absolute" 12 | TRUE_VALUE = "True" 13 | 14 | 15 | class LabelOptions(StrEnum): 16 | DETAILED = "detailed" 17 | SIMPLE = "simple" 18 | HIDE = "hide" 19 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._datetime_utils import date_from_str, date_to_str 2 | from ._simulation_time_series_onebyone_datamodel import ( 3 | SimulationTimeSeriesOneByOneDataModel, 4 | create_tornado_table, 5 | create_vector_selector_data, 6 | get_tornado_data, 7 | ) 8 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_utils/_datetime_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | def date_from_str(date_str: str) -> datetime.datetime: 5 | return datetime.datetime.strptime(date_str, "%Y-%m-%d") 6 | 7 | 8 | def date_to_str(date: datetime.datetime) -> str: 9 | if date.hour != 0 or date.minute != 0 or date.second != 0 or date.microsecond != 0: 10 | raise ValueError( 11 | f"Invalid date resolution, expected no data for hour, minute, second" 12 | f" or microsecond for {str(date)}" 13 | ) 14 | return date.strftime("%Y-%m-%d") 15 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import OneByOneView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._general_settings import GeneralSettings 2 | from ._selections import Selections 3 | from ._sensitivity_filter import SensitivityFilter 4 | from ._vizualisation import Visualization 5 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_settings/_sensitivity_filter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | from dash.development.base_component import Component 6 | from webviz_config.utils import StrEnum 7 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 8 | 9 | 10 | class SensitivityFilter(SettingsGroupABC): 11 | class Ids(StrEnum): 12 | SENSITIVITY_FILTER = "sensitivity-filter" 13 | SENSITIVITY_FILTER_LINK = "sensitivity-filter-link" 14 | 15 | def __init__(self, sensitivities: List[str]) -> None: 16 | super().__init__("Sensitivity Filter") 17 | self._sensitivities = sensitivities 18 | 19 | def layout(self) -> List[Component]: 20 | return [ 21 | html.Div( 22 | style={"margin-bottom": "10px"}, 23 | children=[ 24 | wcc.Checklist( 25 | id=self.register_component_unique_id( 26 | self.Ids.SENSITIVITY_FILTER_LINK 27 | ), 28 | options=[ 29 | { 30 | "label": "Apply filter only on timeseries", 31 | "value": "no link", 32 | } 33 | ], 34 | value=[], 35 | ), 36 | ], 37 | ), 38 | wcc.SelectWithLabel( 39 | id=self.register_component_unique_id(self.Ids.SENSITIVITY_FILTER), 40 | options=[{"label": i, "value": i} for i in self._sensitivities], 41 | value=self._sensitivities, 42 | size=min(20, len(self._sensitivities)), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_settings/_vizualisation.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | from dash.development.base_component import Component 6 | from webviz_config.utils import StrEnum 7 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 8 | 9 | from ...._types import LineType 10 | 11 | 12 | class Visualization(SettingsGroupABC): 13 | class Ids(StrEnum): 14 | REALIZATION_OR_MEAN = "realization-or-mean" 15 | BOTTOM_VISUALIZATION = "bottom-visualization" 16 | 17 | def __init__( 18 | self, 19 | ) -> None: 20 | super().__init__("Visualization") 21 | 22 | def layout(self) -> List[Component]: 23 | return [ 24 | wcc.RadioItems( 25 | id=self.register_component_unique_id(self.Ids.REALIZATION_OR_MEAN), 26 | options=[ 27 | {"label": "Individual realizations", "value": LineType.REALIZATION}, 28 | {"label": "Mean over Sensitivities", "value": LineType.STATISTICS}, 29 | ], 30 | value=LineType.REALIZATION, 31 | ), 32 | html.Div( 33 | style={"margin-top": "10px"}, 34 | children=wcc.RadioItems( 35 | label="Bottom visualization:", 36 | id=self.register_component_unique_id(self.Ids.BOTTOM_VISUALIZATION), 37 | options=[ 38 | {"label": "Table", "value": "table"}, 39 | {"label": "Realization plot", "value": "realplot"}, 40 | ], 41 | vertical=False, 42 | value="table", 43 | ), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from ._bottom_visualization_view_element import BottomVisualizationViewElement 2 | from ._general_view_element import GeneralViewElement 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_view_elements/_bottom_visualization_view_element.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from dash import dash_table, html 3 | from webviz_config.utils import StrEnum 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | 6 | 7 | class BottomVisualizationViewElement(ViewElementABC): 8 | class Ids(StrEnum): 9 | TABLE_WRAPPER = "table-wrapper" 10 | TABLE = "table" 11 | REAL_GRAPH_WRAPPER = "real-graph-wrapper" 12 | REAL_GRAPH = "real-graph" 13 | 14 | def __init__(self) -> None: 15 | super().__init__() 16 | 17 | def inner_layout(self) -> html.Div: 18 | return html.Div( 19 | children=[ 20 | html.Div( 21 | id=self.register_component_unique_id(self.Ids.TABLE_WRAPPER), 22 | style={"display": "block"}, 23 | children=dash_table.DataTable( 24 | id=self.register_component_unique_id(self.Ids.TABLE), 25 | sort_action="native", 26 | sort_mode="multi", 27 | filter_action="native", 28 | style_as_list_view=True, 29 | style_table={"height": "35vh", "overflowY": "auto"}, 30 | ), 31 | ), 32 | html.Div( 33 | id=self.register_component_unique_id(self.Ids.REAL_GRAPH_WRAPPER), 34 | style={"display": "none"}, 35 | children=wcc.Graph( 36 | config={"displayModeBar": False}, 37 | style={"height": "35vh"}, 38 | id=self.register_component_unique_id(self.Ids.REAL_GRAPH), 39 | ), 40 | ), 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_simulation_time_series_onebyone/_views/_onebyone_view/_view_elements/_general_view_element.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from dash import html 3 | from webviz_config.utils import StrEnum 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | 6 | 7 | class GeneralViewElement(ViewElementABC): 8 | class Ids(StrEnum): 9 | GRAPH = "graph" 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def inner_layout(self) -> html.Div: 15 | return html.Div( 16 | children=[ 17 | wcc.Graph( 18 | id=self.register_component_unique_id(self.Ids.GRAPH), 19 | style={"height": "43.5vh"}, 20 | ) 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/__init__.py: -------------------------------------------------------------------------------- 1 | from .structural_uncertainty import StructuralUncertainty 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .dialog_controller import open_dialogs 2 | from .intersection_controller import update_intersection 3 | from .intersection_source_controller import update_intersection_source 4 | from .map_controller import update_maps 5 | from .realization_filter_controller import update_realizations 6 | from .uncertainty_table_controller import update_uncertainty_table 7 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/controllers/dialog_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional 2 | 3 | from dash import MATCH, Dash, Input, Output, State 4 | from dash.exceptions import PreventUpdate 5 | 6 | 7 | def open_dialogs( 8 | app: Dash, 9 | get_uuid: Callable, 10 | ) -> None: 11 | @app.callback( 12 | Output( 13 | {"id": get_uuid("dialog"), "dialog_id": MATCH, "element": "wrapper"}, 14 | "open", 15 | ), 16 | Input( 17 | {"id": get_uuid("dialog"), "dialog_id": MATCH, "element": "button-open"}, 18 | "n_clicks", 19 | ), 20 | State( 21 | {"id": get_uuid("dialog"), "dialog_id": MATCH, "element": "wrapper"}, 22 | "open", 23 | ), 24 | ) 25 | def _toggle_dialog_graph_settings(n_open: int, is_open: bool) -> Optional[bool]: 26 | """Open or close graph settings dialog button""" 27 | if n_open: 28 | return not is_open 29 | raise PreventUpdate 30 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/figures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_structural_uncertainty/figures/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .clientside_stores import clientside_stores 2 | from .intersection_and_map import intersection_and_map_layout 3 | from .intersection_data import intersection_data_layout 4 | from .map_data import map_data_layout 5 | from .realization_filter import realization_layout 6 | from .uncertainty_table import uncertainty_table_layout 7 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/views/clientside_stores.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Union 2 | 3 | from dash import dcc, html 4 | 5 | 6 | def clientside_stores( 7 | get_uuid: Callable, 8 | initial_settings: Dict, 9 | realizations: List[Union[str, int]], 10 | ) -> html.Div: 11 | """Contains the clientside stores""" 12 | return html.Div( 13 | children=[ 14 | dcc.Store( 15 | id=get_uuid("intersection-graph-data"), storage_type="session", data=[] 16 | ), 17 | dcc.Store( 18 | id=get_uuid("initial-intersection-graph-layout"), 19 | data=initial_settings.get("intersection_layout", {}), 20 | ), 21 | dcc.Store( 22 | id=get_uuid("realization-store"), 23 | data=initial_settings.get("intersection-data", {}).get( 24 | "realizations", realizations 25 | ), 26 | ), 27 | dcc.Store( 28 | id={"id": get_uuid("map"), "element": "stored_polyline"}, 29 | storage_type="session", 30 | ), 31 | dcc.Store( 32 | id={"id": get_uuid("map"), "element": "stored_xline"}, 33 | storage_type="session", 34 | ), 35 | dcc.Store( 36 | id={"id": get_uuid("map"), "element": "stored_yline"}, 37 | storage_type="session", 38 | ), 39 | dcc.Store( 40 | id=get_uuid("intersection-graph-layout"), 41 | storage_type="session", 42 | ), 43 | dcc.Store( 44 | id={ 45 | "id": get_uuid("intersection-data"), 46 | "element": "stored_manual_update_options", 47 | }, 48 | storage_type="session", 49 | ), 50 | dcc.Store( 51 | id=get_uuid("map-color-ranges"), 52 | storage_type="session", 53 | ), 54 | ] 55 | ) 56 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/views/dialog.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import dash_bootstrap_components as dbc 4 | import webviz_core_components as wcc 5 | from dash import html 6 | 7 | 8 | def dialog_layout( 9 | uuid: str, 10 | dialog_id: str, 11 | title: str, 12 | children: List, 13 | size: str = "lg", 14 | ) -> wcc.Dialog: 15 | return wcc.Dialog( 16 | # style={"marginTop": "20vh"}, 17 | children=children, 18 | id={"id": uuid, "dialog_id": dialog_id, "element": "wrapper"}, 19 | max_width=size, 20 | title=title, 21 | draggable=True, 22 | ) 23 | 24 | 25 | def open_dialog_layout(uuid: str, dialog_id: str, title: str) -> dbc.Button: 26 | return html.Div( 27 | children=html.Button( 28 | title, 29 | id={"id": uuid, "dialog_id": dialog_id, "element": "button-open"}, 30 | ), 31 | ) 32 | 33 | 34 | def clear_all_apply_dialog_buttons( 35 | uuid: str, dialog_id: str, apply_disabled: bool = True 36 | ) -> html.Div: 37 | return html.Div( 38 | children=[ 39 | dbc.Button( 40 | "Clear", 41 | style={"padding": "0 20px"}, 42 | className="mr-1", 43 | id={"id": uuid, "dialog_id": dialog_id, "element": "clear"}, 44 | ), 45 | dbc.Button( 46 | "All", 47 | style={"padding": "0 20px"}, 48 | className="mr-1", 49 | id={"id": uuid, "dialog_id": dialog_id, "element": "all"}, 50 | ), 51 | dbc.Button( 52 | "Apply", 53 | style={"padding": "0 20px", "visibility": "hidden"} 54 | if apply_disabled 55 | else {"padding": "0 20px"}, 56 | className="mr-1", 57 | id={"id": uuid, "dialog_id": dialog_id, "element": "apply"}, 58 | ), 59 | ] 60 | ) 61 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_structural_uncertainty/views/realization_filter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | 6 | 7 | def realization_layout( 8 | uuid: str, realizations: List[int], value: List[int] 9 | ) -> html.Div: 10 | """Layout for the realization filter dialog""" 11 | return html.Div( 12 | style={"marginTop": "10px"}, 13 | children=html.Label( 14 | children=[ 15 | wcc.Select( 16 | id={"id": uuid, "element": "realizations"}, 17 | options=[{"label": real, "value": real} for real in realizations], 18 | value=[str(val) for val in value], 19 | multi=True, 20 | size=20, 21 | persistence=True, 22 | persistence_type="session", 23 | ), 24 | ] 25 | ), 26 | ) 27 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_swatinit_qc/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import SwatinitQC 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import TornadoPlotterFMU 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/_error.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | 4 | def error(error_message: str) -> html.Div: 5 | return html.Div(children=error_message, style={"color": "red"}) 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/shared_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._filters import Filters 2 | from ._selectors import Selectors 3 | from ._view_settings import FilterOption, Scale, ViewSettings 4 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/shared_settings/_selectors.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class Selectors(SettingsGroupABC): 10 | class IDs(StrEnum): 11 | RESPONSE = "response" 12 | 13 | def __init__( 14 | self, 15 | responses: List[str], 16 | initial_response: str, 17 | ) -> None: 18 | super().__init__("Selectors") 19 | self._responses = responses 20 | self._initial_response = initial_response 21 | 22 | def layout(self) -> List[Component]: 23 | return [ 24 | wcc.Dropdown( 25 | id=self.register_component_unique_id(Selectors.IDs.RESPONSE), 26 | label="Response", 27 | options=[{"label": i, "value": i} for i in self._responses], 28 | value=self._initial_response, 29 | multi=False, 30 | clearable=False, 31 | ) 32 | ] 33 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_tornado_plotter_fmu/views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/plot_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plot_view import PlotSettings, TornadoPlotView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/plot_view/view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from .plot import TornadoPlot 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/plot_view/view_elements/plot.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class TornadoPlot(ViewElementABC): 7 | class IDs(StrEnum): 8 | GRAPH = "graph" 9 | 10 | def __init__(self, height: str = "86vh") -> None: 11 | super().__init__() 12 | self.height = height 13 | 14 | def inner_layout(self) -> wcc.Graph: 15 | return wcc.Graph( 16 | id=self.register_component_unique_id(TornadoPlot.IDs.GRAPH), 17 | config={"displayModeBar": True}, 18 | style={"display": "block", "height": self.height}, 19 | ) 20 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/table_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._table_view import TornadoTableView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/table_view/view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from .table import TornadoTable 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_tornado_plotter_fmu/views/table_view/view_elements/table.py: -------------------------------------------------------------------------------- 1 | from dash import dash_table 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class TornadoTable(ViewElementABC): 7 | class IDs(StrEnum): 8 | TABLE = "table" 9 | 10 | def __init__(self, height: str = "75vh") -> None: 11 | super().__init__() 12 | self.height = height 13 | 14 | def inner_layout(self) -> dash_table.DataTable: 15 | return dash_table.DataTable( 16 | id=self.register_component_unique_id(TornadoTable.IDs.TABLE), 17 | style_cell={"whiteSpace": "normal", "height": "auto"}, 18 | ) 19 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import VfpAnalysis 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Optional, Tuple 2 | 3 | from webviz_config import WebvizPluginABC, WebvizSettings 4 | from webviz_config.utils import StrEnum 5 | 6 | from ._utils import VfpDataModel 7 | from ._views._vfp_view import VfpView 8 | 9 | 10 | class VfpAnalysis(WebvizPluginABC): 11 | """Vizualizes VFP curves. 12 | 13 | --- 14 | 15 | * **`vfp_file_pattern`:** File pattern for where to search for vfp arrow files. The path 16 | should be relative to the runpath if ensemble and realization is given as input, if not 17 | the path needs to be absolute. 18 | * **`ensemble`:** Which ensemble in `shared_settings` to use. 19 | * **`realization`:** Which realization to pick from the ensemble. 20 | --- 21 | 22 | The plugin uses an `.arrow` representation of the VFP curves, which can be exported to disk by 23 | using the `RES2CSV` forward model in ERT with subcommand `vfp`. 24 | 25 | So far, the plugin only vizualizes VFPPROD curves, but the plan is to extend it also to 26 | VFPINJ curves soon. 27 | 28 | """ 29 | 30 | class Ids(StrEnum): 31 | VFP_VIEW = "vpf-view" 32 | 33 | def __init__( 34 | self, 35 | webviz_settings: WebvizSettings, 36 | vfp_file_pattern: str = "share/results/tables/vfp/*.arrow", 37 | ensemble: Optional[str] = None, 38 | realization: Optional[int] = None, 39 | ) -> None: 40 | super().__init__(stretch=True) 41 | 42 | self._datamodel = VfpDataModel( 43 | webviz_settings=webviz_settings, 44 | vfp_file_pattern=vfp_file_pattern, 45 | ensemble=ensemble, 46 | realization=realization, 47 | ) 48 | 49 | self.add_view(VfpView(self._datamodel), self.Ids.VFP_VIEW) 50 | 51 | def add_webvizstore(self) -> List[Tuple[Callable, List[Dict]]]: 52 | return self._datamodel.webviz_store 53 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class PressureType(StrEnum): 5 | BHP = "BHP" 6 | DP = "DP" 7 | 8 | 9 | class VfpParam(StrEnum): 10 | THP = "THP" 11 | WFR = "WFR" 12 | GFR = "GFR" 13 | ALQ = "ALQ" 14 | RATE = "RATE" 15 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._vfp_data_model import VfpDataModel, VfpTable 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_vfp_analysis/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import VfpView 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._filters import Filters 2 | from ._pressure_option import PressureOption 3 | from ._selections import Selections 4 | from ._vizualisation import Vizualisation 5 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_settings/_filters.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class Filters(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | THP_LABEL = "thp-label" 12 | WFR_LABEL = "wfr-label" 13 | GFR_LABEL = "gfr-label" 14 | ALQ_LABEL = "alq-label" 15 | THP = "thp" 16 | WFR = "wfr" 17 | GFR = "gfr" 18 | ALQ = "alq" 19 | 20 | def __init__(self) -> None: 21 | super().__init__("Filters") 22 | 23 | def layout(self) -> List[Any]: 24 | return [ 25 | html.Label( 26 | id=self.register_component_unique_id(Filters.Ids.THP_LABEL), 27 | ), 28 | wcc.SelectWithLabel( 29 | id=self.register_component_unique_id(Filters.Ids.THP), 30 | size=6, 31 | ), 32 | html.Label( 33 | id=self.register_component_unique_id(Filters.Ids.WFR_LABEL), 34 | ), 35 | wcc.SelectWithLabel( 36 | id=self.register_component_unique_id(Filters.Ids.WFR), 37 | size=6, 38 | ), 39 | html.Label( 40 | id=self.register_component_unique_id(Filters.Ids.GFR_LABEL), 41 | ), 42 | wcc.SelectWithLabel( 43 | id=self.register_component_unique_id(Filters.Ids.GFR), 44 | size=6, 45 | ), 46 | html.Label( 47 | id=self.register_component_unique_id(Filters.Ids.ALQ_LABEL), 48 | ), 49 | wcc.SelectWithLabel( 50 | id=self.register_component_unique_id(Filters.Ids.ALQ), 51 | size=6, 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_settings/_pressure_option.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | import webviz_core_components as wcc 4 | from webviz_config.utils import StrEnum 5 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 6 | 7 | from ...._types import PressureType 8 | 9 | 10 | class PressureOption(SettingsGroupABC): 11 | class Ids(StrEnum): 12 | PRESSURE_OPTION = "pressure-option" 13 | 14 | def __init__(self) -> None: 15 | super().__init__("Pressure Option") 16 | 17 | def layout(self) -> List[Any]: 18 | return [ 19 | wcc.RadioItems( 20 | id=self.register_component_unique_id( 21 | PressureOption.Ids.PRESSURE_OPTION 22 | ), 23 | options=[ 24 | {"label": "BHP", "value": PressureType.BHP}, 25 | {"label": "DP (BHP-THP)", "value": PressureType.DP}, 26 | ], 27 | value=PressureType.BHP, 28 | persistence=True, 29 | persistence_type="session", 30 | ) 31 | ] 32 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_settings/_vizualisation.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | import webviz_core_components as wcc 4 | from webviz_config.utils import StrEnum 5 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 6 | 7 | from ...._types import VfpParam 8 | 9 | 10 | class Vizualisation(SettingsGroupABC): 11 | class Ids(StrEnum): 12 | COLOR_BY = "color-by" 13 | 14 | def __init__(self, vfp_names: List[str]) -> None: 15 | super().__init__("Vizualisation") 16 | self._vfp_names = vfp_names 17 | 18 | def layout(self) -> List[Any]: 19 | return [ 20 | wcc.Dropdown( 21 | id=self.register_component_unique_id(Vizualisation.Ids.COLOR_BY), 22 | label="Color by", 23 | options=[ 24 | {"label": "THP", "value": VfpParam.THP}, 25 | {"label": "WFR", "value": VfpParam.WFR}, 26 | {"label": "GFR", "value": VfpParam.GFR}, 27 | {"label": "ALQ", "value": VfpParam.ALQ}, 28 | ], 29 | clearable=False, 30 | value=VfpParam.THP, 31 | persistence=True, 32 | persistence_type="session", 33 | ) 34 | ] 35 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._vfp_figure_builder import VfpFigureBuilder 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_view_elements/__init__.py: -------------------------------------------------------------------------------- 1 | from ._vfp_graph import VfpGraph 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_vfp_analysis/_views/_vfp_view/_view_elements/_vfp_graph.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from dash import html 3 | from webviz_config.utils import StrEnum 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | 6 | 7 | class VfpGraph(ViewElementABC): 8 | class Ids(StrEnum): 9 | GRAPH = "graph" 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def inner_layout(self) -> html.Div: 15 | return wcc.Graph( 16 | id=self.register_component_unique_id(self.Ids.GRAPH), 17 | style={"height": "87vh"}, 18 | ) 19 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from .volumetric_analysis import VolumetricAnalysis 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .comparison_controllers import comparison_controllers 2 | from .distribution_controllers import distribution_controllers 3 | from .export_data_controllers import export_data_controllers 4 | from .fipfile_qc_controller import fipfile_qc_controller 5 | from .layout_controllers import layout_controllers 6 | from .selections_controllers import selections_controllers 7 | from .tornado_controllers import tornado_controllers 8 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/controllers/export_data_controllers.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | import pandas as pd 4 | from dash import ALL, Input, Output, State, callback, callback_context, dcc 5 | from dash.exceptions import PreventUpdate 6 | 7 | 8 | def export_data_controllers(get_uuid: Callable) -> None: 9 | @callback( 10 | Output(get_uuid("download-dataframe"), "data"), 11 | Input({"request": "table_data", "table_id": ALL}, "data_requested"), 12 | State({"table_id": ALL}, "data"), 13 | State({"table_id": ALL}, "columns"), 14 | State({"request": "table_data", "table_id": ALL}, "id"), 15 | State({"table_id": ALL}, "id"), 16 | prevent_initial_call=True, 17 | ) 18 | def _export_table_data( 19 | _data_requested: list, 20 | table_data: list, 21 | table_columns: list, 22 | button_ids: list, 23 | table_ids: list, 24 | ) -> Callable: 25 | ctx = callback_context.triggered[0] 26 | export_clicks = { 27 | id_value["table_id"]: n_clicks 28 | for id_value, n_clicks in zip(button_ids, _data_requested) 29 | } 30 | table_to_extract = [x for x in export_clicks.keys() if x in ctx["prop_id"]] 31 | if not table_to_extract or export_clicks[table_to_extract[0]] is None: 32 | raise PreventUpdate 33 | 34 | index = [x["table_id"] for x in table_ids].index(table_to_extract[0]) 35 | table_data = table_data[index] 36 | table_columns = [x["name"] for x in table_columns[index]] 37 | 38 | return dcc.send_data_frame( 39 | pd.DataFrame(data=table_data, columns=table_columns).to_excel, 40 | "VolumetricAnalysis.xlsx", 41 | index=False, 42 | ) 43 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_volumetric_analysis/utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/utils/utils.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | from typing import List, Tuple 3 | 4 | import dash 5 | 6 | 7 | def update_relevant_components(id_list: list, update_info: List[dict]) -> list: 8 | output_id_list = [dash.no_update] * len(id_list) 9 | for elm in update_info: 10 | for idx, x in enumerate(id_list): 11 | if all(x[key] == value for key, value in elm["conditions"].items()): 12 | output_id_list[idx] = elm["new_value"] 13 | break 14 | return output_id_list 15 | 16 | 17 | def move_to_end_of_list(element: str, list_of_elements: list) -> list: 18 | return [x for x in list_of_elements if x != element] + [element] 19 | 20 | 21 | def to_ranges(list_of_integers: List[int]) -> List[Tuple[int, int]]: 22 | """Return list of tuples with ranges from list of integers""" 23 | ranges = [] 24 | for _, group in itertools.groupby( 25 | enumerate(sorted(list_of_integers)), lambda t: t[1] - t[0] 26 | ): 27 | int_range = list(group) 28 | ranges.append((int_range[0][1], int_range[-1][1])) 29 | return ranges 30 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .clientside_stores import clientside_stores 2 | from .main_view import main_view 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_volumetric_analysis/views/clientside_stores.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from dash import dcc, html 4 | 5 | 6 | def clientside_stores(get_uuid: Callable) -> html.Div: 7 | """Contains the clientside stores""" 8 | return html.Div( 9 | children=[ 10 | dcc.Store(id=get_uuid("selections"), storage_type="session"), 11 | dcc.Store(id=get_uuid("page-selected"), storage_type="session"), 12 | dcc.Store(id=get_uuid("voldist-page-selected"), storage_type="session"), 13 | dcc.Store(id=get_uuid("initial-load-info"), storage_type="memory"), 14 | html.Div( 15 | style={"display": "none"}, 16 | children=dcc.Download(id=get_uuid("download-dataframe")), 17 | ), 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import WellAnalysis 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_types.py: -------------------------------------------------------------------------------- 1 | from webviz_config.utils import StrEnum 2 | 3 | 4 | class PressurePlotMode(StrEnum): 5 | MEAN = "mean" 6 | SINGLE_REAL = "single-real" 7 | 8 | 9 | class NodeType(StrEnum): 10 | WELL = "well" 11 | GROUP = "group" 12 | WELL_BH = "well-bh" 13 | 14 | 15 | class ChartType(StrEnum): 16 | BAR = "bar" 17 | PIE = "pie" 18 | AREA = "area" 19 | 20 | 21 | class StatType(StrEnum): 22 | MEAN = "mean" 23 | P10 = "p10" 24 | P50 = "p50" 25 | P90 = "p90" 26 | MAX = "max" 27 | MIN = "min" 28 | P10_MINUS_P90 = "p10-p90" 29 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._ensemble_well_analysis_data import EnsembleWellAnalysisData 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_well_analysis/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_control_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import WellControlView 2 | from ._view_element import WellControlViewElement 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_control_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._well_control_figure import create_well_control_figure 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_control_view/_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class WellControlViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | CHART = "chart" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div( 15 | id=self.register_component_unique_id(WellControlViewElement.Ids.CHART) 16 | ) 17 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import WellOverviewView 2 | from ._view_element import WellOverviewViewElement 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from ._chart_type import WellOverviewChartType 2 | from ._filters import WellOverviewFilters 3 | from ._layout_options import WellOverviewLayoutOptions 4 | from ._selections import WellOverviewSelections 5 | from ._statistical_options import WellOverviewStatisticalOptions 6 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_settings/_chart_type.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | from ...._types import ChartType 9 | 10 | 11 | class WellOverviewChartType(SettingsGroupABC): 12 | class Ids(StrEnum): 13 | CHARTTYPE = "charttype" 14 | 15 | def __init__(self) -> None: 16 | super().__init__("Chart Type") 17 | 18 | def layout(self) -> List[Component]: 19 | return [ 20 | wcc.RadioItems( 21 | id=self.register_component_unique_id(self.Ids.CHARTTYPE), 22 | options=[ 23 | {"label": "Bar chart", "value": ChartType.BAR}, 24 | {"label": "Pie chart", "value": ChartType.PIE}, 25 | {"label": "Stacked area chart", "value": ChartType.AREA}, 26 | ], 27 | value=ChartType.BAR, 28 | vertical=True, 29 | ) 30 | ] 31 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_settings/_filters.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash.development.base_component import Component 5 | from webviz_config.utils import StrEnum 6 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 7 | 8 | 9 | class WellOverviewFilters(SettingsGroupABC): 10 | class Ids(StrEnum): 11 | SELECTED_WELLS = "selected-wells" 12 | 13 | def __init__(self, wells: List[str]) -> None: 14 | super().__init__("Filters") 15 | self._wells = wells 16 | 17 | def layout(self) -> List[Component]: 18 | return [ 19 | wcc.SelectWithLabel( 20 | label="Well", 21 | size=min(10, len(self._wells)), 22 | id=self.register_component_unique_id( 23 | WellOverviewFilters.Ids.SELECTED_WELLS 24 | ), 25 | options=[{"label": well, "value": well} for well in self._wells], 26 | value=self._wells, 27 | multi=True, 28 | ) 29 | ] 30 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_settings/_statistical_options.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import webviz_core_components as wcc 4 | from dash import html 5 | from dash.development.base_component import Component 6 | from webviz_config.utils import StrEnum 7 | from webviz_config.webviz_plugin_subclasses import SettingsGroupABC 8 | 9 | from ...._types import StatType 10 | 11 | 12 | class WellOverviewStatisticalOptions(SettingsGroupABC): 13 | class Ids(StrEnum): 14 | STATISTICS = "statistics" 15 | 16 | def __init__(self) -> None: 17 | super().__init__("Statistics") 18 | 19 | def layout(self) -> List[Component]: 20 | return [ 21 | html.Div( 22 | children=[ 23 | wcc.RadioItems( 24 | id=self.register_component_unique_id(self.Ids.STATISTICS), 25 | options=[ 26 | {"label": "Mean", "value": StatType.MEAN}, 27 | {"label": "P10 (high)", "value": StatType.P10}, 28 | { 29 | "label": "P50 (median)", 30 | "value": StatType.P50, 31 | }, 32 | {"label": "P90 (low)", "value": StatType.P90}, 33 | {"label": "Maximum", "value": StatType.MAX}, 34 | {"label": "Minimum", "value": StatType.MIN}, 35 | {"label": "P10 - P90", "value": StatType.P10_MINUS_P90}, 36 | ], 37 | value=StatType.MEAN, 38 | ) 39 | ], 40 | ) 41 | ] 42 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._well_overview_figure import WellOverviewFigure, format_well_overview_figure 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_analysis/_views/_well_overview_view/_view_element.py: -------------------------------------------------------------------------------- 1 | import webviz_core_components as wcc 2 | from dash import html 3 | from webviz_config.utils import StrEnum 4 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 5 | 6 | 7 | class WellOverviewViewElement(ViewElementABC): 8 | class Ids(StrEnum): 9 | GRAPH = "graph" 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def inner_layout(self) -> html.Div: 15 | return html.Div( 16 | children=wcc.Graph( 17 | id=self.register_component_unique_id(WellOverviewViewElement.Ids.GRAPH), 18 | style={"height": "87vh"}, 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completion/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import WellCompletion 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completion/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._well_completion_data_model import WellCompletionDataModel 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completion/_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_well_completion/_views/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completion/_views/_well_completion_view/__init__.py: -------------------------------------------------------------------------------- 1 | from ._view import WellCompletionView 2 | from ._view_element import WellCompletionViewElement 3 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completion/_views/_well_completion_view/_view_element.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | from webviz_config.utils import StrEnum 3 | from webviz_config.webviz_plugin_subclasses import ViewElementABC 4 | 5 | 6 | class WellCompletionViewElement(ViewElementABC): 7 | class Ids(StrEnum): 8 | COMPONENT = "component" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def inner_layout(self) -> html.Div: 14 | return html.Div( 15 | id=self.register_component_unique_id( 16 | WellCompletionViewElement.Ids.COMPONENT 17 | ) 18 | ) 19 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completions/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plugin import WellCompletions 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_completions/_callbacks.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any, Callable, Dict, List 3 | 4 | import webviz_subsurface_components as wsc 5 | from dash import Dash, Input, Output 6 | 7 | from ._business_logic import WellCompletionsDataModel 8 | from ._layout import LayoutElements 9 | 10 | 11 | def plugin_callbacks( 12 | app: Dash, get_uuid: Callable, data_models: Dict[str, WellCompletionsDataModel] 13 | ) -> None: 14 | @app.callback( 15 | Output(get_uuid(LayoutElements.WELL_COMPLETIONS_COMPONENT), "children"), 16 | Output(get_uuid(LayoutElements.WELL_COMPLETIONS_COMPONENT), "style"), 17 | Input(get_uuid(LayoutElements.ENSEMBLE_DROPDOWN), "value"), 18 | ) 19 | def _render_well_completions(ensemble_name: str) -> list: 20 | data = json.load(data_models[ensemble_name].create_ensemble_dataset()) 21 | 22 | no_leaves = count_leaves(data["stratigraphy"]) 23 | return [ 24 | wsc.WellCompletions(id="well_completions", data=data), 25 | { 26 | "padding": "10px", 27 | "height": no_leaves * 50 + 180, 28 | "min-height": 500, 29 | "width": "98%", 30 | }, 31 | ] 32 | 33 | 34 | def count_leaves(stratigraphy: List[Dict[str, Any]]) -> int: 35 | """Counts the number of leaves in the stratigraphy tree""" 36 | return sum( 37 | count_leaves(zonedict["subzones"]) if "subzones" in zonedict else 1 38 | for zonedict in stratigraphy 39 | ) 40 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from .well_log_viewer import WellLogViewer 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/_validate_log_templates.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Dict, List 3 | 4 | import yaml 5 | 6 | 7 | def load_and_validate_log_templates(log_templates: List[Path]) -> Dict[str, Any]: 8 | validated_templates = {} 9 | for idx, template_path in enumerate(log_templates): 10 | template = yaml.safe_load(template_path.read_text()) 11 | # Validate against json schema here when available 12 | # https://github.com/equinor/webviz-subsurface-components/issues/508 13 | template_name = template.get("name", f"template_{idx+1}") 14 | validated_templates[template_name] = template 15 | return validated_templates 16 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from ._well_controller import well_controller 2 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/controllers/_well_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, Tuple 2 | 3 | from dash import Dash, Input, Output 4 | 5 | from webviz_subsurface._models.well_set_model import WellSetModel 6 | 7 | from ..utils.xtgeo_well_log_to_json import xtgeo_well_logs_to_json_format 8 | 9 | 10 | def well_controller( 11 | app: Dash, 12 | well_set_model: WellSetModel, 13 | log_templates: Dict, 14 | get_uuid: Callable, 15 | ) -> None: 16 | @app.callback( 17 | Output(get_uuid("well-log-viewer"), "welllog"), 18 | Output(get_uuid("well-log-viewer"), "template"), 19 | Input(get_uuid("well"), "value"), 20 | Input(get_uuid("template"), "value"), 21 | ) 22 | def _update_log_data(well_name: str, template: str) -> Tuple[Any, Any]: 23 | well = well_set_model.get_well(well_name) 24 | return xtgeo_well_logs_to_json_format(well), log_templates.get(template) 25 | -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/webviz-subsurface/2559b7a68d886df915da7f647872f3ac7aacab7f/webviz_subsurface/plugins/_well_log_viewer/utils/__init__.py -------------------------------------------------------------------------------- /webviz_subsurface/plugins/_well_log_viewer/utils/xtgeo_well_log_to_json.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Optional 2 | 3 | import xtgeo 4 | 5 | 6 | def xtgeo_well_logs_to_json_format(well: xtgeo.Well) -> Dict: 7 | header = generate_header(well_name=well.name) 8 | curves = [] 9 | 10 | # Calculate well geometrics if MD log is not provided 11 | if well.mdlogname is None: 12 | well.geometrics() 13 | 14 | # Add MD and TVD curves 15 | curves.append(generate_curve(log_name="MD", description="Measured depth")) 16 | curves.append( 17 | generate_curve(log_name="TVD", description="True vertical depth (SS)") 18 | ) 19 | # Add additonal logs, skipping geometrical logs if calculated 20 | lognames = [ 21 | logname 22 | for logname in well.lognames 23 | if logname not in ["Q_MDEPTH", "Q_AZI", "Q_INCL", "R_HLEN"] 24 | ] 25 | for logname in lognames: 26 | curves.append(generate_curve(log_name=logname.upper())) 27 | 28 | # Filter dataframe to only include relevant logs 29 | curve_names = [well.mdlogname, "Z_TVDSS"] + lognames 30 | dframe = well.dataframe[curve_names] 31 | dframe = dframe.reindex(curve_names, axis=1) 32 | 33 | return {"header": header, "curves": curves, "data": dframe.values.tolist()} 34 | 35 | 36 | def generate_header(well_name: str, logrun_name: str = "log") -> Dict[str, Any]: 37 | return { 38 | "name": logrun_name, 39 | "well": well_name, 40 | } 41 | 42 | 43 | def generate_curve( 44 | log_name: str, description: Optional[str] = None, value_type: str = "float" 45 | ) -> Dict[str, Any]: 46 | return { 47 | "name": log_name, 48 | "description": description, 49 | "valueType": value_type, 50 | "dimensions": 1, 51 | "unit": "m", 52 | "quantity": None, 53 | "axis": None, 54 | "maxSize": 20, 55 | } 56 | --------------------------------------------------------------------------------