Performance issues with high(er) resolution images in dash

Hey there,

I have an app where I want to show three to four gray-scale images with px.imshow(binary_string=True). I have to update the images via a callback and I’m running into performance issues if the resolution gets higher.

Up to a size of approximately 1100 x 3500 pixels, the loading time is OK. If I try do show higher resolution images, the loading time explodes. I know that’s normal but I would really like to reduce loading times / increase resolution.

Maybe someone has an idea. :hugs:

Here an example:

import dash
from dash import Input, Output, dcc, ALL
import dash_bootstrap_components as dbc

import plotly.express as px
import numpy as np

WIDTH = 1100
HEIGHT = 3500


def create_graph():
    return dcc.Graph(
        figure=px.imshow(
            img=np.random.randint(0, 255, size=(WIDTH, HEIGHT)).astype(np.uint8),
            binary_string=True,
            binary_compression_level=4
        )
    )


app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP]
)

app.layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(
                    id={'type': 'graph_container', 'index': 0},
                    width=6
                ),
                dbc.Col(
                    id={'type': 'graph_container', 'index': 1},
                    width=6
                ),
            ],
        ),
        dbc.Row(
            [
                dbc.Col(
                    id={'type': 'graph_container', 'index': 2},
                    width=6
                ),
                dbc.Col(
                    id={'type': 'graph_container', 'index': 3},
                    width=6
                ),
            ],
        ),
        dbc.Row(
            dbc.Button(
                id={'type': 'btn', 'index': 0},
                children='click'
            )
        ),
    ],
    fluid=True
)


@app.callback(
    Output({'type': 'graph_container', 'index': ALL}, 'children'),
    Input({'type': 'btn', 'index': ALL}, 'n_clicks'),
    prevent_initial_call=True
)
def update_figures(_):
    return create_graph(), create_graph(), create_graph(), create_graph()


if __name__ == "__main__":
    app.run(debug=False)

Hey @AIMPED,

What do you think of splitting up to something like this?

import dash
from dash import Input, Output, dcc, ALL, MATCH
import dash_bootstrap_components as dbc

import plotly.express as px
import numpy as np

WIDTH = 1100
HEIGHT = 3500


def create_graph():
    return px.imshow(
            img=np.random.randint(0, 255, size=(WIDTH, HEIGHT)).astype(np.uint8),
            binary_string=True,
            binary_compression_level=4
    )


app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    suppress_callback_exceptions=True
)

app.layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(
                    dcc.Loading(id={'type': 'graph_container', 'index': 0}),
                    width=6
                ),
                dbc.Col(
                    dcc.Loading(id={'type': 'graph_container', 'index': 1}),
                    width=6
                ),
            ],
        ),
        dbc.Row(
            [
                dbc.Col(
                    dcc.Loading(id={'type': 'graph_container', 'index': 2}),
                    width=6
                ),
                dbc.Col(
                    dcc.Loading(id={'type': 'graph_container', 'index': 3}),
                    width=6
                ),
            ],
        ),
        dbc.Row(
            dbc.Button(
                id={'type': 'btn', 'index': 0},
                children='click'
            )
        ),
    ],
    fluid=True
)


@app.callback(
    Output({'type': 'graph_container', 'index': ALL}, 'children'),
    Input({'type': 'btn', 'index': ALL}, 'n_clicks'),
    prevent_initial_call=True
)
def add_graphs(_):
    return [dcc.Graph(id={'type': 'graph', 'index': x}) for x in range(4)]

@app.callback(
    Output({'type': 'graph', 'index': MATCH}, 'figure'),
    Input({'type': 'graph', 'index': MATCH}, 'id'),
)
def update_figures(_):
    return create_graph()


if __name__ == "__main__":
    app.run(debug=True)

HI @jinnyzor,

Thanks for the suggestion. :raised_hands:

This seems to be faster, or at least, you can see a progress instead of just waiting for all figures to display. My app is quite complex and I’m not sure, if I can change my callbacks to this approach.

What I have notied however, is that the waiting time for the server response is quite high in both approaches.

Is there a way to reduce the waiting time for the server response? I’m not too familiar with hosting options, server configurations and so on. For my test I just use the built in local serving option.

When you host on a server with multiple workers, I think that the simultaneous requests with get routed to different workers, thus making the response quicker.

I was looking into a way to create the graph on the clientside, which you might be able to use plotly JS for this, and pass only the data required from the server.