Skip to main content

Customize the image

This guide shows how to configure the currently used image of the annotator. The image property can be configured in different ways. The following example is a minimal demo of configuring the image. It provides a button triggering the change of the image, and an annotator initialized with image=None.

demo_image.py
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()

After loading the component, since image=None, the annotator will render a placeholder showing that no image is available. In this case, the annotator is not usable.

Load the image by assets

The basic usage of the image configuration is to use the static assets. By configuring the dash application like this:

dash = Dash(assets_folder="path/to/the/assets")

The images can be put into the assets folder on which server device the dashboard is running. In the callback, the image can be specified by the site path to the asset file.

@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

When clicking the button, the image path/to/the/assets/a-test-image.png will be loaded and displayed to users, and the annotator will be usable.

Apparently, this method is not quite useful because it can only load the images in the static assets folder. In other words, all the images need to be prepared before running the dashboard, which is not a good way to process the dynamic data in real practice.

Load the image by encoded data string

A possible way to dynamically load the image is using the encoded data string. The data string can serve as the URL of the <img src=...>. It is also usable for this annotator.

tip

See more information in the Data URLs documentation.

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

In this example, the image is loaded from a PNG file and encoded as a Base64 string subsequently. Note that:

  1. The Base64 encoding is necessary for most kinds of images, except for image/svg+xml. In other words, by using this image loading method, users need to use Base64 in most cases.
  2. When returning the data URL, the prefix like data:image/...;base64, is necessary. In particular, the image/... is the mimetype of the image to be loaded. It needs to be consistent with the image read from the file.
tip

See more information in the MiME type documentation.

The most important benefit of using this method is to allow the image to be loaded dynamically. In other words, the image can be loaded from a PIL.Image.Image or an IO object like io.BytesIO.

danger

It is important to know that the length of such data URLs is limited. Different browsers may have different limitations to this length. See Data URLs documentation. In some cases, even if the length does not exceed the limit, there may still have some unexpected behaviors. Therefore, we recommend users to use this method when the image file size does not exceed 8 MB.

Load the image by dash-file-cache

Since the data URL has a size limitation, it is preferred to use a more secure way to dynamically serve the images to the browser. The best way to do this is to write a flask service by flask.View. However, writing a service is a little bit complicated. If users do not want to write the service by themselves, please try our friend project: Dash File Cache.

demo_with_dfc.py
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()

In the above example, we provide a service by the instance dfc.ServiceData(...). It can put a file to the cache, and return the address that is used for accessing the file. Since the file is cached and will be delivered to the users by the file stream, the file size does not have a limitation in this case.