在 Plotly-Dash 中显示带有 dcc.Graph 的图像

Displaying an image with dcc.Graph in Plotly-Dash

我正在尝试在 Plotly-Dash 中显示带有 dcc.Graph 组件的图像,因此我得到了它漂亮的工具栏和内置 UI。但是我收到了一堆错误。这就是我的程序:

  1. 上传 JPG 图片。
  2. 使用np.array(Image.open(..))加载图像。
  3. px.imshow()转换为数字。
  4. 通过 dcc.Graph 组件传递并尝试在页面上显示它。

下面是我的代码:

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)

我收到以下错误:

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...

this answer 中所述,您需要从图像字符串中删除 data:image/png;base64,。如果您按如下方式更新 parse_contents 函数,您的应用应该可以运行:

def parse_contents(contents, filename, date):

    # Remove 'data:image/png;base64' from the image string,
    # see 
    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
        )
    ])

完整代码:

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 
    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')