【问题标题】:Displaying PIL Images in Dash/Plotly在 Dash/Plotly 中显示 PIL 图像
【发布时间】:2020-03-16 20:13:00
【问题描述】:

我一直在开发我的 Dash Web 应用程序,现在正在考虑将它托管在我的 VM 上。

设置环境后,我无法直接在html.Img 元素中加载PIL Image 对象。 当它们被渲染时,会弹出一个错误并通知我我的 PIL 图像不可序列化。

这让我觉得很奇怪,而且可能不是情节错误,但我的代码、库和图像完全相同,导致我的 VM 上出现错误,但在我的工作站上运行顺利。

加载并进行一些预处理后,我的 Image 对象被传递给 html 组件,如下所示:

grid_main_images = <PIL.Image.Image image mode=RGB size=482x542 at 0x7FE88C04CD90>

html.Img(src=grid_main_imgs)

同样,序列化错误仅发生在我的 VM 上,而不发生在我的本地计算机上。 这是完整的错误/回溯

Traceback (most recent call last):
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/dash/dash.py", line 1227, in add_context
    cls=plotly.utils.PlotlyJSONEncoder
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/_plotly_utils/utils.py", line 49, in encode
    encoded_o = super(PlotlyJSONEncoder, self).encode(o)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/_plotly_utils/utils.py", line 119, in default
    return _json.JSONEncoder.default(self, obj)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Image is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/dash/dash.py", line 1291, in dispatch
    response.set_data(self.callback_map[output]['callback'](*args))
  File "/home/aegilsson/anaconda3/envs/diamond/lib/python3.7/site-packages/dash/dash.py", line 1242, in add_context
    ).replace('    ', ''))
dash.exceptions.InvalidCallbackReturnValue: 
The callback for property `children`
of component `tabs-content` returned a value
which is not JSON serializable.

In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
or lists of those.

【问题讨论】:

  • 建议您尝试将 src 更改为子项(如docs),因为 src 需要字符串并且您发送的 Pillow 对象不是字符串。此外,您可能希望在 base64 中转换枕头对象(来自 here) - 这将为您提供一个字符串,您可以将其包含在 children 中
  • 感谢您的意见。我设法在dash-image-processing repo 中找到了这个dash_reusable_components.py 实用模块

标签: python-3.x flask plotly plotly-dash


【解决方案1】:

您需要对图像进行 base64 编码并添加一些 HTML 标头。

def pil_to_b64(im, enc_format="png", **kwargs):
    """
    Converts a PIL Image into base64 string for HTML displaying
    :param im: PIL Image object
    :param enc_format: The image format for displaying. If saved the image will have that extension.
    :return: base64 encoding
    """

    buff = BytesIO()
    im.save(buff, format=enc_format, **kwargs)
    encoded = base64.b64encode(buff.getvalue()).decode("utf-8")

    return encoded

html.Img(id="my-img",className="image", src="data:image/png;base64, " + pil_to_b64(pil_img))

致谢:@Atli 的评论为我指明了正确的方向。

【讨论】:

    【解决方案2】:

    不确定何时引入,但 plotly.express.imshow() 和 plotly.graph_objects.Figure().add_layout_image() 都接受开箱即用的 PIL 图像:

    事实上,如果您提供任何不兼容的输入,例如 np.array 到 fig.add_layout_image({"source":np.array(my_pil_img)}),您将得到以下 ValueError:

    'source' 属性是一个图像 URI,可以指定为:

    • 远程图像 URI 字符串(例如“http://www.somewhere.com/image.png”)
    • 数据 URI 图像字符串(例如 'data:image/png;base64,iVBORw0KGgoAAAANSU')
    • 一个 PIL.Image.Image 对象,它将立即转换为数据 URI 图像字符串 请参阅http://pillow.readthedocs.io/en/latest/reference/Image.html

    例子:

    from PIL import Image
    #import pathlib
    
    mysize = (512,512)
    
    # if you have a real img
    #path_to_file = pathlib.Path().cwd()/'dummy.png'
    #img = Image.open(path_to_file)
    
    # use dummy img here for example:
    img = Image.new('RGBA', size=(1024, 1024), color=(155, 0, 0))
    
    
    # I encountered issues with very big images, so best to make an in-place thumbnail
    img = img.thumbnail(mysize, Image.ANTIALIAS)
    

    现在plotly.express:

    import plotly.express as px
    px.imshow(img)
    

    或者更详细一点的plotly.graph_objects:

    from plotly import graph_objects as go
    fig = go.Figure()
    
    # based on https://plotly.com/python/images/#zoom-on-static-images
    # Constants
    img_width, img_height = img.size
    scale_factor = 1
    
    # Add invisible scatter trace.
    # This trace is added to help the autoresize logic work.
    fig.add_trace(
        go.Scatter(
            x=[0, img_width * scale_factor],
            y=[0, img_height * scale_factor],
            mode="markers",
            marker_opacity=0
        )
    )
    
    # Configure axes
    fig.update_xaxes(
        visible=False,
        range=[0, img_width * scale_factor]
    )
    
    fig.update_yaxes(
        visible=False,
        range=[0, img_height * scale_factor],
        # the scaleanchor attribute ensures that the aspect ratio stays constant
        scaleanchor="x"
    )
    
    # Add image
    fig.add_layout_image(
        dict(
            x=0,
            sizex=img_width * scale_factor,
            y=img_height * scale_factor,
            sizey=img_height * scale_factor,
            xref="x",
            yref="y",
            opacity=1.0,
            layer="below",
            sizing="stretch",
            source=img)
    )
    
    # Configure other layout
    fig.update_layout(
        width=img_width * scale_factor,
        height=img_height * scale_factor,
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
    )
    
    # Disable the autosize on double click because it adds unwanted margins around the image
    # More detail: https://plotly.com/python/configuration-options/
    fig.show(config={'doubleClick': 'reset'})
    

    【讨论】:

      猜你喜欢
      • 2022-07-30
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 2019-06-25
      • 2021-08-02
      • 1970-01-01
      • 2021-11-04
      • 1970-01-01
      相关资源
      最近更新 更多