Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedding or displaying local images #71

Closed
FaustineLi opened this issue Jul 5, 2017 · 27 comments
Closed

Embedding or displaying local images #71

FaustineLi opened this issue Jul 5, 2017 · 27 comments

Comments

@FaustineLi
Copy link

I can't find a way to embed or display local images, either through html or in a plotly graph. This seems to be a limitation with the server can do (in the former case) or a lack of support (in the later case).

My intended use case is have a user point to a directory of images. I want to display the image that the user selects. The image is then classified using a model built in Keras.

Is there any way to display an image locally?

@chriddyp
Copy link
Member

chriddyp commented Jul 5, 2017

Great question @FaustineLi !

Serving general static content isn't supported out of the box with Dash (yet). Here are two workarounds for now:
1 - You can serve files by adding a static file route to the underlying dash server.
2 - You can serve files by starting a separate file server like Python's built-in file server ($ python -m SimpleHTTPServer 8000) or something like nginx or apache.
3 - You can upload images to a 3rd party image hosting site like Amazon S3

1 is the simplest way to go and works great for just running apps on your own machine. Here's a simple example that displays PNGs from my personal Desktop (mostly screenshots of Plotly!).

Note that if you deploy this app, you'll want to be careful that the static file serving route is only serving files from a single directory (otherwise, malicious users might be able to view any file on the server). This example includes a whitelist of files and only serves files from the Desktop.

import dash
import dash_core_components as dcc
import dash_html_components as html

import flask
import glob
import os

image_directory = '/Users/chriddyp/Desktop/'
list_of_images = [os.path.basename(x) for x in glob.glob('{}*.png'.format(image_directory))]
static_image_route = '/static/'

app = dash.Dash()

app.layout = html.Div([
    dcc.Dropdown(
        id='image-dropdown',
        options=[{'label': i, 'value': i} for i in list_of_images],
        value=list_of_images[0]
    ),
    html.Img(id='image')
])

@app.callback(
    dash.dependencies.Output('image', 'src'),
    [dash.dependencies.Input('image-dropdown', 'value')])
def update_image_src(value):
    return static_image_route + value

# Add a static image route that serves images from desktop
# Be *very* careful here - you don't want to serve arbitrary files
# from your computer or server
@app.server.route('{}<image_path>.png'.format(static_image_route))
def serve_image(image_path):
    image_name = '{}.png'.format(image_path)
    if image_name not in list_of_images:
        raise Exception('"{}" is excluded from the allowed static files'.format(image_path))
    return flask.send_from_directory(image_directory, image_name)

if __name__ == '__main__':
    app.run_server(debug=True)

Here's what the app looks like:
image-serving

@chriddyp
Copy link
Member

chriddyp commented Jul 5, 2017

Closing this for now. Feel free to reopen if this solution doesn't work for you!

@chriddyp chriddyp closed this as completed Jul 5, 2017
@FaustineLi
Copy link
Author

Thanks! That definitely worked!

@chriddyp
Copy link
Member

Another way to display images without adding an extra route is to base64 encode them:

import dash
import dash_html_components as html
import base64

app = dash.Dash()

image_filename = 'my-image.png' # replace with your own image
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
    html.Img(src='data:image/png;base64,{}'.format(encoded_image))
])

if __name__ == '__main__':
    app.run_server(debug=True)

@shapiromatron
Copy link

shapiromatron commented Jul 28, 2017

Thanks, this was helpful. This is especially useful if you want to bundle your standard css resources in a relative path in a project. I feel like this might be a really common use-case that may be good to bake into the docs; if you're interested @chriddyp I could submit a PR.

I'm coming from the django-world; here's a minimal example for static serving any arbitrary resource from a /static/ path, using a pattern similar to django static files in debug mode (docs):

import dash
import flask

STATIC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
app = dash.Dash()

@app.server.route('/static/<resource>')
def serve_static(resource):
    return flask.send_from_directory(STATIC_PATH, resource)

app.css.append_css({
    'external_url': '/static/site.css',
})

if __name__ == '__main__':
    app.run_server()

@zshujon
Copy link

zshujon commented Aug 8, 2017

I've the same problem with Video.
Please tell me what to do?

@chriddyp
Copy link
Member

chriddyp commented Aug 8, 2017

@zshujon - could you open an issue in the community forum? More folks are likely to help you out with implementation details there: https://community.plot.ly/c/dash

@cfeng2016
Copy link

I found the base64 encoding approach works in general but doesn't work if I am working under virtualenv. Why and is there a work around? Thanks.

@chriddyp
Copy link
Member

@cfeng2016 - Can you create a small reproducable example?

@cfeng2016
Copy link

cfeng2016 commented Aug 18, 2017

@chriddyp It looks like the problem is system/python dependent. My Mac with Python 3.6.2 under virtualenv has problem but Ubuntu with Python 2 does not (with or without virtualenv). I checked the html code. The problem came when it is

<img src="data:image/png;base64,b'......'" class=......>

Removing 'b'' parts solves the problem. So I think it's from the return of the function base64.b64encode()

@cfeng2016
Copy link

@chriddyp - It looks like the Python version causes the difference. Using decode() function will fix it regardless of Python environment.

import dash
import dash_html_components as html
import base64

app = dash.Dash()
image_filename = 'my-image.png' # replace with your own image
encoded_image = base64.b64encode(open(image_filename, 'rb').read())
app.layout = html.Div([
    html.Img(src='data:image/png;base64,{}'.format(encoded_image.decode()))
])

if __name__ == '__main__':
    app.run_server(debug=True)

@ianwaring
Copy link

I know it's a bit around the houses with Base64 versions - but how do I adjust the size of the image using html.Img() once I have it? Trying to find any documentation to see if there are methods I can invoke to alter the size! Any help, guidance etc would really help my sanity :-}

@chriddyp
Copy link
Member

@ianwaring - try out the style parameter which sets inline css like:

html.Image(src='...', style={'width': '500px'})

I recommend messing around with these CSS inline styles by using Chrome's dev tools: https://developers.google.com/web/tools/chrome-devtools/inspect-styles/.

@eloiup
Copy link

eloiup commented Sep 29, 2017

hey chris,

the base64 solution worked great for me, but the image is too small. I used the style option within html.image, but i was wondering if there were other ways to edit it.

I have also looked into https://plot.ly/dash/dash-html-components for reference. But it doesnt have that many examples. For example, i wld like to change its position, but i am unsure to the proper argument for it.

Also, for some reason, the image is located above a few dropdown menus i built in the dash. I have no idea why, since i declare 'em previous to the image.

edit: for some reason the style={'width': '20%', 'display': 'inline-block'}) within the dropdowns were causing em to go below the image.
edit: tryied "align":"middle" on img style without success. i was able to manually set the paddings with "margin":"x px y px ..." but the auto command leaves the img attached to the left border instead of centralized

@chriddyp
Copy link
Member

Hey @eloiup - Yeah, sounds like CSS issue. style translates to inline css, so that is the correct argument to modify its size and position. I recommend playing around with this live in the Chrome's CSS editor: https://developers.google.com/web/tools/chrome-devtools/inspect-styles/

@chaotic-enigma
Copy link

chaotic-enigma commented May 14, 2018

How do I Update single image as per the input given. I have tried, and it gives big chunk errors saying

RuntimeError: main thread is not in main loop.

My input is basically a date and the output image is a graph. I have provided input section in my dash app but when I give my input, no image displays except the error in my terminal.

I think for this case, we cannot use base64 method. Is there any Different method?

@ghost
Copy link

ghost commented Aug 19, 2018

I am trying to do something similar for Plotly 3D surface plots, but it would load the image. Do I need to make some changes in go.Layout()? I want the image on x-y quadrant.

@chriddyp
Copy link
Member

I am trying to do something similar for Plotly 3D surface plots, but it would load the image

What do you mean by "load the image"? Do you mean, render the 3D surface plot as an image? If so, then that's a slightly different issue than the one being discussed here.

If you just want an image to be embedded inside the graph, then you can use the layout.images object, see https://plot.ly/python/images/. I'm not sure if that's suppored in 3D plots or not, but you could search through issues in https://github.com/plotly/plotly.js/issues/ if it isn't supported.

@dbjohnsonii
Copy link

dbjohnsonii commented Dec 27, 2018

Another way to display images without adding an extra route is to base64 encode them:

import dash
import dash_html_components as html
import base64

app = dash.Dash()

image_filename = 'my-image.png' # replace with your own image
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
    html.Img(src='data:image/png;base64,{}'.format(encoded_image))
])

if __name__ == '__main__':
    app.run_server(debug=True)

Hello, I recognize that this string may be closed but I explored the 64base option with no success. I used the script below and it only returned the broken image icon. Has anything changed since this conversation in 2017? FYI I am using Python Version 3.7.0

import dash
import dash_html_components as html
import base64

app = dash.Dash()

image_filename = 'my-image.png'
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
html.Img(src='data:image/png;base64,{}'.format(encoded_image))
])

if name == 'main':
app.run_server(debug=True)

@02Joyce
Copy link

02Joyce commented Jan 24, 2019

@dbjohnsonii Hi, you could see the chriddyp's reply in front of you.
Then you could try to modify the line 8 in your code as the following.
html.Img(src='data:image/png;base64,{}'.format(encoded_image.decode()))

@dougollerenshaw
Copy link

@chriddyp: I'm having trouble adapting the examples above to a general case. Specifically, when the images are in different folders, the 'app.server.route' method you first posted can't switch between locations.

Alternatively, I tried starting with the base64 example, but I can't figure out how to change the image with the dropdown callback. I get the message 'In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
or lists of those' when I try to return the base64 encoded image.

Would it be possible for you to post a very simple bit of example code that does the following:

  • starts with a list of paths to images in different folders (eg. ['/some/path1/img1.png', '/some/path2/img2'])
  • displays those paths in a dropbdown
  • displays the selected image in as an html.Img

@dougollerenshaw
Copy link

@chriddyp: I've come up with a solution that a solution that works:

import dash
import dash_html_components as html
import dash_core_components as dcc
import base64

app = dash.Dash()

list_of_images = [
    '/image_path_1/img_1.png',
    '/image_path_2/img_2.png'
]

app.layout = html.Div([
    dcc.Dropdown(
        id='image-dropdown',
        options=[{'label': i, 'value': i} for i in list_of_images],
        # initially display the first entry in the list
        value=list_of_images[0]
    ),
    html.Img(id='image')
])


@app.callback(
    dash.dependencies.Output('image', 'src'),
    [dash.dependencies.Input('image-dropdown', 'value')])
def update_image_src(image_path):
    # print the image_path to confirm the selection is as expected
    print('current image_path = {}'.format(image_path))
    encoded_image = base64.b64encode(open(image_path, 'rb').read())
    return 'data:image/png;base64,{}'.format(encoded_image.decode())


if __name__ == '__main__':
    app.run_server(debug=True, port=8053, host='0.0.0.0')

@bakirillov
Copy link

bakirillov commented Nov 24, 2019

Another way to display images without adding an extra route is to base64 encode them:

import dash
import dash_html_components as html
import base64

app = dash.Dash()

image_filename = 'my-image.png' # replace with your own image
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
    html.Img(src='data:image/png;base64,{}'.format(encoded_image))
])

if __name__ == '__main__':
    app.run_server(debug=True)

That doesn't work at all. Image is never shown.

@jvschoen
Copy link

jvschoen commented Apr 10, 2020

Another way to display images without adding an extra route is to base64 encode them:

import dash
import dash_html_components as html
import base64

app = dash.Dash()

image_filename = 'my-image.png' # replace with your own image
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
    html.Img(src='data:image/png;base64,{}'.format(encoded_image))
])

if __name__ == '__main__':
    app.run_server(debug=True)

That doesn't work at all. Image is never shown.
@bakirillov Did you try html.Img(src='data:image/png;base64,{}'.format(encoded_image.decode()))

@chriddyp
Copy link
Member

Good point! .decode() might be needed in some cases.

Let's move any conversation about this out of the closed-issue bug tracker and into the community forum, where there are many more people available to help & debug user's environments: https://community.plotly.com/c/dash

@mrnsnsntd
Copy link

Hello!

Sorry, but this code doesn't work in my app:

import dash
import dash_html_components as html
import dash_core_components as dcc
import base64

app = dash.Dash()

list_of_images = [
'/image_path_1/img_1.png',
'/image_path_2/img_2.png'
]

app.layout = html.Div([
dcc.Dropdown(
id='image-dropdown',
options=[{'label': i, 'value': i} for i in list_of_images],
# initially display the first entry in the list
value=list_of_images[0]
),
html.Img(id='image')
])

@app.callback(
dash.dependencies.Output('image', 'src'),
[dash.dependencies.Input('image-dropdown', 'value')])
def update_image_src(image_path):
# print the image_path to confirm the selection is as expected
print('current image_path = {}'.format(image_path))
encoded_image = base64.b64encode(open(image_path, 'rb').read())
return 'data:image/png;base64,{}'.format(encoded_image.decode())

if name == 'main':
app.run_server(debug=True, port=8053, host='0.0.0.0')

The picture doesn't show. I attach a image showing that point.
Could someone help me, please?
Captura1

@chriddyp
Copy link
Member

@mrnsnsntd - As I mentioned above, could you please move this into the community forum? https://community.plotly.com/c/dash There will be more people to help you there with image paths.

@plotly plotly locked as resolved and limited conversation to collaborators Apr 17, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests