Created
June 4, 2020 12:15
-
-
Save sciunto/aa27a804877a11a81f3a100fc369f287 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import dash | |
from dash.dependencies import Input, Output, State | |
import dash_html_components as html | |
import dash_core_components as dcc | |
from skimage import io, transform, filters, segmentation | |
from skimage import img_as_ubyte | |
from scipy import ndimage | |
import plotly.express as px | |
import plotly.graph_objects as go | |
from dash_canvas.utils import array_to_data_url, image_string_to_PILImage | |
def make_figure(img_array): | |
img_uri = array_to_data_url(img_array) | |
height, width = img_array.shape[0], img_array.shape[1] | |
fig = go.Figure() | |
# Add trace | |
fig.add_trace( | |
go.Scatter(x=[], y=[]) | |
) | |
# Add images | |
fig.add_layout_image( | |
dict( | |
source=img_uri, | |
xref="x", | |
yref="y", | |
x=0, | |
y=0, | |
sizex=width, | |
sizey=height, | |
sizing="contain", | |
layer="below" | |
) | |
) | |
fig.update_layout(template=None) | |
fig.update_xaxes(showgrid=False, range=(0, width), | |
showticklabels=False, | |
zeroline=False) | |
fig.update_yaxes(showgrid=False, scaleanchor='x', range=(height, 0), | |
showticklabels=False, | |
zeroline=False) | |
fig.update_xaxes(showgrid=False) | |
fig.update_yaxes(showgrid=False) | |
return fig | |
def _find_contour(img): | |
threshold = filters.threshold_otsu(img) | |
mask = img < threshold | |
mask = ndimage.binary_fill_holes(mask) | |
img = img_as_ubyte(segmentation.mark_boundaries(img, mask, mode='thick')) | |
contour = np.nonzero(mask) | |
return img, contour | |
def _crop_to_shape(img, layout_shape): | |
if layout_shape is None: | |
return img | |
else: | |
return img[int(layout_shape['y0']):int(layout_shape['y1']), | |
int(layout_shape['x0']):int(layout_shape['x1'])] | |
img = io.imread('assets/uEye_Image_000827.png') | |
img = img_as_ubyte(transform.rescale(img, 0.5)) | |
img_displayed = img | |
fig = make_figure(img_displayed) | |
fig.update_layout(dragmode='drawrect') | |
app = dash.Dash(__name__) | |
server = app.server | |
app.layout = html.Div(children=[ | |
html.Div([ | |
dcc.Graph(id='graph', figure=fig), | |
dcc.Store(id='store'), | |
dcc.Store(id='store-contour'), | |
], style={'width':'45%'}), | |
dcc.Upload( | |
id='upload-image', | |
children=html.Div([ | |
'Drag and Drop or ', | |
html.A('Select File') | |
]), | |
style={ | |
'width': '100%', | |
'height': '60px', | |
'lineHeight': '60px', | |
'borderWidth': '1px', | |
'borderStyle': 'dashed', | |
'borderRadius': '5px', | |
'textAlign': 'center', | |
'margin': '10px' | |
}, | |
# multiple files to be uploaded? | |
multiple=True, | |
), | |
html.Div(id='output-image-upload'), | |
html.Div([ | |
html.Button('Crop to rectangle', id='crop-button'), | |
html.Button('Back to original shape', id='back-button'), | |
html.Button('Find contour', id='contour-button'), | |
html.Button('Measure surface tension', id='measure-button'), | |
html.H3('Model parameters'), | |
html.H5('Image scale (px/mm)'), | |
dcc.Input(id='input-img_scale', type='number', value=100), | |
html.H5('Min surface tension (mN/m)'), | |
dcc.Input(id='input-min_surface_tension', type='number', value=10), | |
html.H5('Max surface tension (mN/m)'), | |
dcc.Input(id='input-max_surface_tension', type='number', value=80), | |
], style={'width':'45%'}), | |
]) | |
def parse_contents(contents, filename, date): | |
img = np.asarray(image_string_to_PILImage(contents)) | |
return img | |
@app.callback(Output('output-image-upload', 'children'), | |
[Input('upload-image', 'contents')], | |
[State('upload-image', 'filename'), | |
State('upload-image', 'last_modified')]) | |
def update_output(list_of_contents, list_of_names, list_of_dates): | |
if list_of_contents is not None: | |
print(list_of_contents, list_of_names, list_of_dates) | |
print(type(list_of_contents)) | |
global img | |
global img_displayed | |
img = parse_contents(list_of_contents[0], list_of_names[0], list_of_dates[0]) | |
img_displayed = img.copy() | |
children = [img,] | |
return children | |
@app.callback( | |
dash.dependencies.Output('store', 'data'), | |
[dash.dependencies.Input('graph', 'relayoutData')]) | |
def store_rectangle(fig_data): | |
if fig_data is not None and 'shapes' in fig_data: | |
return fig_data['shapes'][-1] | |
else: | |
return dash.no_update | |
@app.callback( | |
[dash.dependencies.Output('graph', 'figure'), | |
dash.dependencies.Output('store-contour', 'data')], | |
[dash.dependencies.Input('crop-button', 'n_clicks'), | |
dash.dependencies.Input('back-button', 'n_clicks'), | |
dash.dependencies.Input('contour-button', 'n_clicks') | |
], | |
[dash.dependencies.State('store', 'data'), | |
]) | |
def update_figure(click_crop, click_back, click_contour, shape_data): | |
global img_displayed | |
#if click_back is None and click_crop is None: | |
# return dash.no_update, dash.no_update | |
#else: | |
ctx = dash.callback_context | |
button_id = ctx.triggered[0]['prop_id'].split('.')[0] | |
print(button_id) | |
contour = None | |
if button_id == 'crop-button': | |
img_displayed = _crop_to_shape(img, shape_data) | |
new_store_data = dash.no_update | |
elif button_id == 'back-button': | |
img_displayed = img | |
elif button_id == 'contour-button': | |
img_crop = _crop_to_shape(img_displayed, shape_data) | |
img_displayed, contour = _find_contour(img_crop) | |
elif button_id == 'measure-button': | |
# Do something | |
pass | |
fig = make_figure(img_displayed) | |
fig.update_layout(dragmode='drawrect') | |
return fig, contour | |
if __name__ == '__main__': | |
app.run_server(debug=True) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment