定制图片
本指南展示了如何设置标记器使用的图片。属性image
有多种设置方式。下例展示了配置图片的一个最小范例。其中提供了一个按钮,按下时触发图片的替换。标记器初始化为image=None
。
from typing import Optional
import dash
from dash import html, Output, Input
import dash_picture_annotation as dpa
app = dash.Dash()
app.layout = html.Div(
(
html.Div(html.Button(id="btn-image", children="Set image")),
dpa.DashPictureAnnotation(id="annotator", data=None, image=None),
)
)
@app.callback(
Output("annotator", "image"),
Input("btn-image", "n_clicks"),
prevent_initial_call=True,
)
def set_image(n_clicks: Optional[int]):
if n_clicks:
image = ... # Do something to get the image.
return image
return dash.no_update
app.run()
组件加载后,由于image=None
,标记器会渲染成一个占位符,显示为 “无可用图片”。在此情况下,标记器是不可用的。
从assets加载图片
最基础的图片配置方案是使用静态资源(assets)文件夹。透过将dash应用配置成如下形式:
dash = Dash(assets_folder="path/to/the/assets")
可以将图片放置在dashboard运行的服务器assets目录下。在callback中,就可以将图片定义为指向资源文件的站点 路径。
@app.callback(
Output("annotator", "image"),
Input("btn-image", "n_clicks"),
prevent_initial_call=True,
)
def set_image(n_clicks: Optional[int]):
if n_clicks:
image = "/a-test-image.png"
return image
return dash.no_update
按下按钮时,就会载入path/to/the/assets/a-test-image.png
图片并展示给用户。此时标记器转换为可用状态。
显然,这种方案并非特别有用,因为它只能从静态资源目录下加载图片。换言之,所有的图片需要在dashboard运行前、预先准备就绪。这就不适应实际业务中,处理动态生成数据的需求。
从编码数据字符串加载图片
另一种可行的思路,是使用编码数据字符串动态加载图片。类似的数据字符串,可以取代<img src=...>
中的URL。它同样也可以用在本项目的标记器中。
要了解更多信息,参见数据URL文档。
import base64
@app.callback(
Output("annotator", "image"),
Input("btn-image", "n_clicks"),
prevent_initial_call=True,
)
def set_image(n_clicks: Optional[int]):
if n_clicks:
with open("a-image-file.png", "rb") as fobj:
image = base64.b64encode(fobj.read()).decode("ascii")
return "data:image/{0};base64,{1}".format("png", get_image())
return dash.no_update
此例中,从一个PNG
文件加载了图片,随后将其编码成了Base64字符串。需要注意的是:
- Base64编码对于绝大多数类型的图片都是必要的,但
image/svg+xml
除外。换言之,使用这种图片加载方法时,在大多时候都需要使用Base64编码。 - 返回数据URL时,形如
data:image/...;base64,
的前缀是必要的。特别地,image/...
的部分代表所载入图片的mimetype。其需要和所读取的图片文件保持一致。
要了解更多信息,参见MiME type文档。
此方案最大的好处,是允许图片的动态加载。换言之,可以从PIL.Image.Image
或一个形如io.BytesIO
的IO对象加载图片。
需要特别小心的是,这类数据URL的 长度是有限的。不同的浏览器有不同的长度限制。参见数据URL文档。在某些情况下,哪怕数据长度没有超出限制,也可能会存在一些预期外的表现。因此,建议用户仅在图片尺寸不超过8 MB
的情况下,使用该方案。
从dash-file-cache
加载图片
既然数据URL有尺寸限制,更好、更安全的做法,当然是设计一个向浏览动态传送图片的服务。最合适的实现方式,是使用flask.View
写一个flask
服务。然而,写服务这件事本身有一点点复杂。如果用户不愿自己来写这种服务,可以尝试使用本项目的友邻项目:Dash File Cache。
from typing import Optional
import dash
from dash import html, Output, Input
import dash_picture_annotation as dpa
import dash_file_cache as dfc
app = dash.Dash()
service = dfc.ServiceData(cache=dfc.CacheFile("./temp"))
service.serve(app)
app.layout = html.Div(
(
html.Div(html.Button(id="btn-image", children="Set image")),
dpa.DashPictureAnnotation(id="annotator", data=None, image=None),
)
)
@app.callback(
Output("annotator", "image"),
Input("btn-image", "n_clicks"),
prevent_initial_call=True,
)
def set_image(n_clicks: Optional[int]):
if n_clicks:
image: str = service.register("path/to-a-image.png", mime_type="image/png")
return image
return dash.no_update
app.run()
上例中,使用实例dfc.ServiceData(...)
提供服务。该实例可以将一个文件置入缓存中,并返回用来访问该缓存文件的地址。因为文件已经被缓存、且是透过文件流的方式传递给用户,故而在此情况下,文件的大小不再受限。