diff --git a/.travis.yml b/.travis.yml index 5636b0e892..2df909ef57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ install: - export PATH=$PATH:$PWD script: - - black --check webviz_subsurface_components setup.py + - black --check webviz_subsurface_components tests examples setup.py - npm run test - npm run linting - npm run build:all diff --git a/bandit.yml b/bandit.yml new file mode 100644 index 0000000000..355ddec1ed --- /dev/null +++ b/bandit.yml @@ -0,0 +1,2 @@ +#Allow use of assert for tests +skips: ['B101'] diff --git a/examples/example_hm.py b/examples/example_hm.py index 572038fed9..637e5fdda3 100644 --- a/examples/example_hm.py +++ b/examples/example_hm.py @@ -10,19 +10,18 @@ import dash_core_components as dcc - def generate_synthetic_data(num_groups, num_iter, num_realizations): """Create synthetic test data. In reality, this data will come from an assisted history matching run. """ - obs_group_names = ['Obs. group ' + str(i) for i in range(num_groups)] + obs_group_names = [f"Obs. group {i}" for i in range(num_groups)] number_dp = np.random.randint(low=10, high=100, size=num_groups) df = pd.DataFrame() for i in range(num_iter): - ensemble_name = 'Iteration ' + str(i) + ensemble_name = f"Iteration {i}" # Random test data following # chisquared distribution (i.e. normal distribution squared): @@ -35,31 +34,34 @@ def generate_synthetic_data(num_groups, num_iter, num_realizations): neg = misfits * (1 - split) for j in range(num_realizations): - realization_name = 'Realization ' + str(j) + realization_name = f"Realization {j}" scale = 1.0 + np.random.rand() * 0.4 realization_pos = scale * pos realization_neg = scale * neg - df = df.append(pd.DataFrame( - OrderedDict([ - ('obs_group_name', obs_group_names), - ('ensemble_name', ensemble_name), - ('realization', realization_name), - ('total_pos', realization_pos), - ('total_neg', realization_neg), - ('number_data_points', number_dp) - ]))) + df = df.append( + pd.DataFrame( + OrderedDict( + [ + ("obs_group_name", obs_group_names), + ("ensemble_name", ensemble_name), + ("realization", realization_name), + ("total_pos", realization_pos), + ("total_neg", realization_neg), + ("number_data_points", number_dp), + ] + ) + ) + ) + + return df.set_index(["obs_group_name", "ensemble_name", "realization"]) - return df.set_index(['obs_group_name', 'ensemble_name', 'realization']) def _get_unsorted_edges(): """P10 - P90 unsorted edge coordinates""" - retval = { - 'low': chi2.ppf(0.1, 1), - 'high': chi2.ppf(0.9, 1) - } + retval = {"low": chi2.ppf(0.1, 1), "high": chi2.ppf(0.9, 1)} return retval @@ -69,13 +71,12 @@ def _get_sorted_edges(number_observation_groups): monte_carlo_iterations = 100000 - sorted_values = np.empty((number_observation_groups, - monte_carlo_iterations)) + sorted_values = np.empty((number_observation_groups, monte_carlo_iterations)) for i in range(monte_carlo_iterations): - sorted_values[:, i] = np.sort(np.random.chisquare( - df=1, - size=number_observation_groups)) + sorted_values[:, i] = np.sort( + np.random.chisquare(df=1, size=number_observation_groups) + ) sorted_values = np.flip(sorted_values, 0) @@ -87,44 +88,45 @@ def _get_sorted_edges(number_observation_groups): # These values are to be used for drawing the stair stepped # sorted P10-P90 area: - coordinates = {'low': list(P10), 'high': list(P90)} + coordinates = {"low": list(P10), "high": list(P90)} return coordinates -class HistoryMatch(): +class HistoryMatch: def __init__(self, data): super(HistoryMatch, self).__init__() - self.data = self._prepareData(data) + self.data = self._prepareData(data) + def get_data(self): return self.data + def _prepareData(self, data): data = data.copy().reset_index() ensemble_labels = data.ensemble_name.unique().tolist() num_obs_groups = len(data.obs_group_name.unique()) - data['avg_pos'] = data['total_pos'] / data['number_data_points'] - data['avg_neg'] = data['total_neg'] / data['number_data_points'] + data["avg_pos"] = data["total_pos"] / data["number_data_points"] + data["avg_neg"] = data["total_neg"] / data["number_data_points"] iterations = [] for ensemble in ensemble_labels: df = data[data.ensemble_name == ensemble] - iterations.append(df.groupby('obs_group_name').mean()) + iterations.append(df.groupby("obs_group_name").mean()) sorted_iterations = self._sortIterations(iterations) - iterations_dict = self._iterations_to_dict(sorted_iterations, - ensemble_labels) + iterations_dict = self._iterations_to_dict(sorted_iterations, ensemble_labels) confidence_sorted = _get_sorted_edges(num_obs_groups) confidence_unsorted = _get_unsorted_edges() data = {} - data['iterations'] = iterations_dict - data['confidence_interval_sorted'] = confidence_sorted - data['confidence_interval_unsorted'] = confidence_unsorted + data["iterations"] = iterations_dict + data["confidence_interval_sorted"] = confidence_sorted + data["confidence_interval_unsorted"] = confidence_unsorted return data @@ -135,9 +137,9 @@ def _sortIterations(self, iterations): sorted_df = df.copy() sorted_data.append( - sorted_df.assign(f=sorted_df['avg_pos'] + sorted_df['avg_neg']) - .sort_values('f', ascending=False) - .drop('f', axis=1) + sorted_df.assign(f=sorted_df["avg_pos"] + sorted_df["avg_neg"]) + .sort_values("f", ascending=False) + .drop("f", axis=1) ) return sorted_data @@ -146,26 +148,29 @@ def _iterations_to_dict(self, iterations, labels): retval = [] for iteration, label in zip(iterations, labels): - retval.append({ - 'name': label, - 'positive': iteration['avg_pos'].tolist(), - 'negative': iteration['avg_neg'].tolist(), - 'labels': iteration.index.tolist() - }) + retval.append( + { + "name": label, + "positive": iteration["avg_pos"].tolist(), + "negative": iteration["avg_neg"].tolist(), + "labels": iteration.index.tolist(), + } + ) return retval -data = generate_synthetic_data(num_groups=50, - num_iter=4, - num_realizations=100) -app = dash.Dash(__name__) +data = generate_synthetic_data(num_groups=50, num_iter=4, num_realizations=100) -app.layout = html.Div(children=[ +app = dash.Dash(__name__) - webviz_subsurface_components.HistoryMatch(id='parameters', - data=HistoryMatch(data).get_data()) - ]) +app.layout = html.Div( + children=[ + webviz_subsurface_components.HistoryMatch( + id="parameters", data=HistoryMatch(data).get_data() + ) + ] +) -if __name__ == '__main__': +if __name__ == "__main__": app.run_server(debug=True) diff --git a/examples/example_layered_map.py b/examples/example_layered_map.py index 8dd8cc6c35..37c65aeaac 100644 --- a/examples/example_layered_map.py +++ b/examples/example_layered_map.py @@ -12,7 +12,7 @@ def array_to_png(Z, shift=True, colormap=False): - '''The layered map dash component takes in pictures as base64 data + """The layered map dash component takes in pictures as base64 data (or as a link to an existing hosted image). I.e. for containers wanting to create pictures on-the-fly from numpy arrays, they have to be converted to base64. This is an example function of how that can be done. @@ -29,159 +29,158 @@ def array_to_png(Z, shift=True, colormap=False): 3) If the array is two-dimensional, the picture is stored as greyscale. Otherwise it is either stored as RGB or RGBA (depending on if the size of the third dimension is three or four, respectively). - ''' + """ Z -= np.nanmin(Z) if shift: - Z *= 254.0/np.nanmax(Z) + Z *= 254.0 / np.nanmax(Z) Z += 1.0 else: - Z *= 255.0/np.nanmax(Z) + Z *= 255.0 / np.nanmax(Z) Z[np.isnan(Z)] = 0 if colormap: if Z.shape[0] != 1: - raise ValueError('The first dimension of a ' - 'colormap array should be 1') + raise ValueError("The first dimension of a " "colormap array should be 1") if Z.shape[1] != 256: - raise ValueError('The second dimension of a ' - 'colormap array should be 256') + raise ValueError( + "The second dimension of a " "colormap array should be 256" + ) if Z.shape[2] not in [3, 4]: - raise ValueError('The third dimension of a colormap ' - 'array should be either 3 or 4') + raise ValueError( + "The third dimension of a colormap " "array should be either 3 or 4" + ) if shift: if Z.shape[2] != 4: - raise ValueError('Can not shift a colormap which ' - 'is not utilizing alpha channel') + raise ValueError( + "Can not shift a colormap which " "is not utilizing alpha channel" + ) else: Z[0][0][3] = 0.0 # Make first color channel transparent if Z.ndim == 2: - image = Image.fromarray(np.uint8(Z), 'L') + image = Image.fromarray(np.uint8(Z), "L") elif Z.ndim == 3: if Z.shape[2] == 3: - image = Image.fromarray(np.uint8(Z), 'RGB') + image = Image.fromarray(np.uint8(Z), "RGB") elif Z.shape[2] == 4: - image = Image.fromarray(np.uint8(Z), 'RGBA') + image = Image.fromarray(np.uint8(Z), "RGBA") else: - raise ValueError('Third dimension of array must ' - 'have length 3 (RGB) or 4 (RGBA)') + raise ValueError( + "Third dimension of array must " "have length 3 (RGB) or 4 (RGBA)" + ) else: - raise ValueError('Incorrect number of dimensions in array') + raise ValueError("Incorrect number of dimensions in array") byte_io = io.BytesIO() - image.save(byte_io, format='png') + image.save(byte_io, format="png") byte_io.seek(0) - base64_data = base64.b64encode(byte_io.read()).decode('ascii') - return f'data:image/png;base64,{base64_data}' + base64_data = base64.b64encode(byte_io.read()).decode("ascii") + return f"data:image/png;base64,{base64_data}" -if __name__ == '__main__': +if __name__ == "__main__": # The data below is a modified version of one of the surfaces # taken from the Volve data set provided by Equinor and the former # Volve Licence partners under CC BY-NC-SA 4.0 license, and only # used here as an example data set. # https://creativecommons.org/licenses/by-nc-sa/4.0/ - map_data = np.loadtxt('./example-data/layered-map-data.npz.gz') + map_data = np.loadtxt("./example-data/layered-map-data.npz.gz") min_value = int(np.nanmin(map_data)) max_value = int(np.nanmax(map_data)) map_data = array_to_png(map_data) - colormap = array_to_png(cm.get_cmap('viridis', 256) - ([np.linspace(0, 1, 256)]), colormap=True) + colormap = array_to_png( + cm.get_cmap("viridis", 256)([np.linspace(0, 1, 256)]), colormap=True + ) layers = [ { - 'name': 'A seismic horizon with colormap', - 'base_layer': True, - 'checked': True, - 'data': - [ - { - 'type': 'image', - 'url': map_data, - 'allowHillshading': True, - 'colormap': colormap, - 'unit': 'm', - 'minvalue': min_value, - 'maxvalue': max_value, - 'bounds': [[432205, 6475078], - [437720, 6481113]] - }, - ] + "name": "A seismic horizon with colormap", + "base_layer": True, + "checked": True, + "data": [ + { + "type": "image", + "url": map_data, + "allowHillshading": True, + "colormap": colormap, + "unit": "m", + "minvalue": min_value, + "maxvalue": max_value, + "bounds": [[432205, 6475078], [437720, 6481113]], + }, + ], }, { - 'name': 'The same map without colormap', - 'base_layer': True, - 'checked': False, - 'data': - [ - { - 'type': 'image', - 'url': map_data, - 'bounds': [[432205, 6475078], - [437720, 6481113]] - } - ] + "name": "The same map without colormap", + "base_layer": True, + "checked": False, + "data": [ + { + "type": "image", + "url": map_data, + "bounds": [[432205, 6475078], [437720, 6481113]], + } + ], }, { - 'name': 'Some overlay layer', - 'base_layer': False, - 'checked': False, - 'data': - [ - { - 'type': 'polygon', - 'positions': [[436204, 6475077], - [438204, 6480077], - [432204, 6475077]], - 'color': 'blue', - 'tooltip': 'This is a blue polygon' - }, - { - 'type': 'circle', - 'center': [435200, 6478000], - 'color': 'red', - 'radius': 2, - 'tooltip': 'This is a red circle' - } - ] - } + "name": "Some overlay layer", + "base_layer": False, + "checked": False, + "data": [ + { + "type": "polygon", + "positions": [ + [436204, 6475077], + [438204, 6480077], + [432204, 6475077], + ], + "color": "blue", + "tooltip": "This is a blue polygon", + }, + { + "type": "circle", + "center": [435200, 6478000], + "color": "red", + "radius": 2, + "tooltip": "This is a red circle", + }, + ], + }, ] - with open('../src/demo/example-data/layered-map.json', 'w') as fh: - json.dump({'layers': layers}, fh) + with open("../src/demo/example-data/layered-map.json", "w") as fh: + json.dump({"layers": layers}, fh) app = dash.Dash(__name__) - app.layout = html.Div(children=[ - webviz_subsurface_components.LayeredMap( - id='volve-map', - layers=layers - ), - html.Pre(id='polyline'), - html.Pre(id='marker'), - html.Pre(id='polygon'), - ]) - - @app.callback(Output('polyline', 'children'), - [Input('volve-map', 'polyline_points')]) + app.layout = html.Div( + children=[ + webviz_subsurface_components.LayeredMap(id="volve-map", layers=layers), + html.Pre(id="polyline"), + html.Pre(id="marker"), + html.Pre(id="polygon"), + ] + ) + + @app.callback( + Output("polyline", "children"), [Input("volve-map", "polyline_points")] + ) def get_edited_line(coords): - return f'Edited polyline: {json.dumps(coords)}' + return f"Edited polyline: {json.dumps(coords)}" - @app.callback(Output('marker', 'children'), - [Input('volve-map', 'marker_point')]) + @app.callback(Output("marker", "children"), [Input("volve-map", "marker_point")]) def get_edited_line(coords): - return f'Edited marker: {json.dumps(coords)}' + return f"Edited marker: {json.dumps(coords)}" - @app.callback(Output('polygon', 'children'), - [Input('volve-map', 'polygon_points')]) + @app.callback(Output("polygon", "children"), [Input("volve-map", "polygon_points")]) def get_edited_line(coords): - return f'Edited closed polygon: {json.dumps(coords)}' + return f"Edited closed polygon: {json.dumps(coords)}" app.run_server(debug=True) diff --git a/examples/example_morris.py b/examples/example_morris.py index 0d31ace3c9..62d75b657d 100644 --- a/examples/example_morris.py +++ b/examples/example_morris.py @@ -4,17 +4,68 @@ app = dash.Dash(__name__) -output = [{'mean': 0.0, 'max': 0.0, 'min': 0.0, 'time': '2000-01-01T00:00:00'}, {'mean': 1821300.2, 'max': 2022804.5, 'min': 900429.1, 'time': '2001-01-01T00:00:00'}, {'mean': 3595926.9, 'max': 5060090.5, 'min': 1161664.8, 'time': '2002-01-01T00:00:00'}, {'mean': 4919365.7, 'max': 7102369.0, 'min': 2150000.5, 'time': '2003-01-01T00:00:00'}] -parameters = [{'main': [0.0, 1327720.5, 3439176.1, 5311292.8], 'name': 'FWL', 'interactions': [0.0, 1116199.0, 2541439.9, 2836076.4]}, {'main': [0.0, 844.65, 5093.1, 12363.55], 'name': 'MULTFLT_F1', 'interactions': [0.0, 1231.4, 4597.0, 13793.5]}, {'main': [0.0, 908911.5, 1506246.1, 2000438.5], 'name': 'RANGE_PAR', 'interactions': [0.0, 1396000.4, 1900671.3, 1933889.5]}, {'main': [0.0, 10.1, 7413.1, 322.3], 'name': 'MULTZ_MIDREEK', 'interactions': [0.0, 211.1, 3098.9, 5619.7]}, {'main': [0.0, 1010601.3, 1822840.3, 2869195.5], 'name': 'AZIMUTH', 'interactions': [0.0, 1262311.8, 1822908.7, 2833047.4]}, {'main': [0.0, 167888.5, 398770.5, 598481.5], 'name': 'MEANPERMMULT', 'interactions': [0.0, 114457.6, 180225.4, 201267.2]}] -parameter = 'FOPT' +output = [ + {"mean": 0.0, "max": 0.0, "min": 0.0, "time": "2000-01-01T00:00:00"}, + { + "mean": 1821300.2, + "max": 2022804.5, + "min": 900429.1, + "time": "2001-01-01T00:00:00", + }, + { + "mean": 3595926.9, + "max": 5060090.5, + "min": 1161664.8, + "time": "2002-01-01T00:00:00", + }, + { + "mean": 4919365.7, + "max": 7102369.0, + "min": 2150000.5, + "time": "2003-01-01T00:00:00", + }, +] +parameters = [ + { + "main": [0.0, 1327720.5, 3439176.1, 5311292.8], + "name": "FWL", + "interactions": [0.0, 1116199.0, 2541439.9, 2836076.4], + }, + { + "main": [0.0, 844.65, 5093.1, 12363.55], + "name": "MULTFLT_F1", + "interactions": [0.0, 1231.4, 4597.0, 13793.5], + }, + { + "main": [0.0, 908911.5, 1506246.1, 2000438.5], + "name": "RANGE_PAR", + "interactions": [0.0, 1396000.4, 1900671.3, 1933889.5], + }, + { + "main": [0.0, 10.1, 7413.1, 322.3], + "name": "MULTZ_MIDREEK", + "interactions": [0.0, 211.1, 3098.9, 5619.7], + }, + { + "main": [0.0, 1010601.3, 1822840.3, 2869195.5], + "name": "AZIMUTH", + "interactions": [0.0, 1262311.8, 1822908.7, 2833047.4], + }, + { + "main": [0.0, 167888.5, 398770.5, 598481.5], + "name": "MEANPERMMULT", + "interactions": [0.0, 114457.6, 180225.4, 201267.2], + }, +] +parameter = "FOPT" -app.layout = html.Div(children=[ - webviz_subsurface_components.Morris( - id='morris_chart', - output=output, - parameters=parameters, - parameter=parameter), - ]) +app.layout = html.Div( + children=[ + webviz_subsurface_components.Morris( + id="morris_chart", output=output, parameters=parameters, parameter=parameter + ), + ] +) -if __name__ == '__main__': +if __name__ == "__main__": app.run_server(debug=True) diff --git a/examples/example_subsurface_map.py b/examples/example_subsurface_map.py index 4528780d68..13d966a51b 100644 --- a/examples/example_subsurface_map.py +++ b/examples/example_subsurface_map.py @@ -3,14 +3,15 @@ import dash_html_components as html import webviz_subsurface_components -with open('../src/demo/example-data/subsurface-map.json') as json_file: + +with open("../src/demo/example-data/subsurface-map.json", "r") as json_file: data = json.load(json_file) app = dash.Dash(__name__) -app.layout = html.Div(children=[ - webviz_subsurface_components.Map(id='reek-map', data=data) - ]) +app.layout = html.Div( + children=[webviz_subsurface_components.Map(id="reek-map", data=data)] +) -if __name__ == '__main__': +if __name__ == "__main__": app.run_server(debug=True) diff --git a/tests/test_hm.py b/tests/test_hm.py index 50320f6c8b..db5bd91b71 100644 --- a/tests/test_hm.py +++ b/tests/test_hm.py @@ -7,20 +7,20 @@ # Basic test for the component rendering. def test_render_hm(dash_duo): - with open('tests/data/hm_data.json', 'r') as json_file: + with open("tests/data/hm_data.json", "r") as json_file: hm_data = json.load(json_file) app = dash.Dash(__name__) - app.layout = html.Div([ - webviz_subsurface_components.HistoryMatch( - id='parameters', - data=hm_data), - ]) + app.layout = html.Div( + [webviz_subsurface_components.HistoryMatch(id="parameters", data=hm_data),] + ) dash_duo.start_server(app) # Get text of first data series - my_component = dash_duo.wait_for_element_by_css_selector('#g_history_matching_plot > text', timeout=4) + my_component = dash_duo.wait_for_element_by_css_selector( + "#g_history_matching_plot > text", timeout=4 + ) - assert 'Misfit overview for Iteration 0' == my_component.text + assert my_component.text == "Misfit overview for Iteration 0" diff --git a/tests/test_morris.py b/tests/test_morris.py index 4dbeea4659..ee33d66ac2 100644 --- a/tests/test_morris.py +++ b/tests/test_morris.py @@ -7,22 +7,26 @@ # Basic test for the component rendering. def test_render(dash_duo): - with open('tests/data/morris_data.json', 'r') as f: + with open("tests/data/morris_data.json", "r") as f: data = json.loads(f.read()) app = dash.Dash(__name__) - app.layout = html.Div([ - webviz_subsurface_components.Morris( - id='morris_chart', - output=data['output'], - parameters=data['parameters'], - parameter=data['parameter']) - ]) + app.layout = html.Div( + [ + webviz_subsurface_components.Morris( + id="morris_chart", + output=data["output"], + parameters=data["parameters"], + parameter=data["parameter"], + ) + ] + ) dash_duo.start_server(app) # Get y-axis text with selenium my_component = dash_duo.wait_for_element_by_css_selector( - '#sensitivity-slider-plot__graph-container > svg > g > text') + "#sensitivity-slider-plot__graph-container > svg > g > text" + ) - assert 'FOPT' == my_component.text + assert my_component.text == "FOPT"