2

I am trying to display an image with with dcc.Graph component in Plotly-Dash so I get its nice toolbars and built-in UI. However I am getting a bunch of errors. This is what my procedure is:

  1. Upload JPG image.
  2. Load image using np.array(Image.open(..)).
  3. Convert to figure with px.imshow().
  4. Pass this through dcc.Graph component and try to display it on page.

Below is my code:

import datetime
import dash
from dash.dependencies import Input, Output, State
from dash import dcc
from dash import html
import numpy as np
from PIL import Image
import plotly.express as px

app = dash.Dash(__name__)

app.layout = html.Div([

    html.Div(
        children=[
            dcc.Upload(
                id='upload-image',
                children=html.Div([
                    'Drag and Drop or ',
                    html.A('Select Files')
                ]),
                # Allow multiple files to be uploaded
                multiple=True)]),
    html.Div(
        children=[
            html.Div(id='output-image-upload'),
        ])
])

def parse_contents(contents, filename, date):
    img = np.array(Image.open(contents))
    fig = px.imshow(img)
    return html.Div([
        html.H5(filename),
        html.H6(datetime.datetime.fromtimestamp(date)),
        dcc.Graph(figure=fig)
    ])
        
@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:
        children = [
            parse_contents(c, n, d) for c, n, d in
            zip(list_of_contents, list_of_names, list_of_dates)]
        return children

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

I get these errors below:

Callback error updating output-image-upload.children

Traceback (most recent call last):
  File "C:\Users\...\test.py", line 48, in update_output
    children = [
  File "C:\Users\...\test.py", line 49, in <listcomp>
    parse_contents(c, n, d) for c, n, d in
  File "C:\Users\...\test.py", line 34, in parse_contents
    img = np.array(Image.open(contents))
  File "C:\Users\...\AppData\Local\Programs\Python\Python38\Lib\site-packages\PIL\Image.py", line 2904, in open
    fp = builtins.open(filename, "rb")
FileNotFoundError: [Errno 2] No such file or directory: 'data:image/jpeg;base64,/9j/2wBDAAYEBQY...
Flavia Giammarino
  • 7,987
  • 11
  • 30
  • 40
mha
  • 141
  • 1
  • 11

1 Answers1

3

As explained in this answer you need to remove data:image/png;base64, from the image string. If you update your parse_contents function as follows your app should work:

def parse_contents(contents, filename, date):

    # Remove 'data:image/png;base64' from the image string,
    # see https://stackoverflow.com/a/26079673/11989081
    data = contents.replace('data:image/png;base64,', '')
    img = Image.open(io.BytesIO(base64.b64decode(data)))

    # Convert the image string to numpy array and create a
    # Plotly figure, see https://plotly.com/python/imshow/
    fig = px.imshow(np.array(img))

    # Hide the axes and the tooltips
    fig.update_layout(
        plot_bgcolor='white',
        paper_bgcolor='white',
        margin=dict(t=20, b=0, l=0, r=0),
        xaxis=dict(
            showgrid=False,
            showticklabels=False,
            linewidth=0
        ),
        yaxis=dict(
            showgrid=False,
            showticklabels=False,
            linewidth=0
        ),
        hovermode=False
    )

    return html.Div([
        html.H5(filename),
        html.H6(datetime.datetime.fromtimestamp(date)),
        dcc.Graph(
            figure=fig,
            config={'displayModeBar': True} # Always display the modebar
        )
    ])

Full code:

import io
import base64
import datetime
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import numpy as np
import plotly.express as px
from PIL import Image

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div(
        children=[
            dcc.Upload(
                id='upload-image',
                children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
                multiple=True
            )
        ]
    ),
    html.Div(
        children=[
            html.Div(id='output-image-upload'),
        ]
    )
])

def parse_contents(contents, filename, date):

    # Remove 'data:image/png;base64' from the image string,
    # see https://stackoverflow.com/a/26079673/11989081
    data = contents.replace('data:image/png;base64,', '')
    img = Image.open(io.BytesIO(base64.b64decode(data)))

    # Convert the image string to numpy array and create a
    # Plotly figure, see https://plotly.com/python/imshow/
    fig = px.imshow(np.array(img))

    # Hide the axes and the tooltips
    fig.update_layout(
        plot_bgcolor='white',
        paper_bgcolor='white',
        margin=dict(t=20, b=0, l=0, r=0),
        xaxis=dict(
            showgrid=False,
            showticklabels=False,
            linewidth=0
        ),
        yaxis=dict(
            showgrid=False,
            showticklabels=False,
            linewidth=0
        ),
        hovermode=False
    )

    return html.Div([
        html.H5(filename),
        html.H6(datetime.datetime.fromtimestamp(date)),
        dcc.Graph(
            figure=fig,
            config={'displayModeBar': True} # Always display the modebar
        )
    ])

@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:
        children = [
            parse_contents(c, n, d) for c, n, d in
            zip(list_of_contents, list_of_names, list_of_dates)
        ]
        return children

if __name__ == '__main__':
    app.run_server(debug=True, host='127.0.0.1')
Flavia Giammarino
  • 7,987
  • 11
  • 30
  • 40
  • I am getting error `PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x000001F6FD158360>` – mha Nov 30 '21 at 20:33
  • I can't reproduce, it works fine for me. Try taking a look at the answers to [this question](https://stackoverflow.com/q/31077366/11989081). If they are not helpful it's probably better if you create a minimal reproducible example of the issue outside Plotly-Dash and ask a new questions (I think that more people are likely to take a look at it if you don't frame it specifically as a Plotly-Dash question). – Flavia Giammarino Nov 30 '21 at 20:46
  • 1
    Figured it out needed to change to /jpeg for me. Thanks. – mha Nov 30 '21 at 21:03
  • Do you know why the image shows up really small now? – mha Nov 30 '21 at 21:18
  • Is it smaller than the actual image size? – Flavia Giammarino Nov 30 '21 at 21:22
  • Yeah it came out quite tiny. – mha Nov 30 '21 at 21:36
  • There's a lot of white space all around and uses up only a tiny portion of space available. Before it filled out the whole page based on my html. – mha Nov 30 '21 at 21:46
  • I just tried with a larger one and indeed it looked smaller and the resolution was low when zooming in. I'm wondering if this is a better approach: https://plotly.com/python/images/#zoom-on-static-images. – Flavia Giammarino Nov 30 '21 at 22:13