How can I input multiple custom images in the form of data instead of background, and control the display of the images through the slider?

import plotly.graph_objects as go
import numpy as np
import base64
import os

from PIL import Image

folder_path = "XXX/visualize/gt/multiview"
file_names = os.listdir(folder_path)
img_paths = [os.path.join(folder_path, file_name) for file_name in file_names if file_name.endswith((".png", ".jpg", ".jpeg"))]
# make figure
fig_dict = {
    "data": [],
    "layout": {},
    "frames": []
}

# fill in most of layout
fig_dict["layout"]["hovermode"] = "closest"
fig_dict["layout"]["updatemenus"] = [
    {
        "buttons": [
            {
                "args": [None, {"frame": {"duration": 500, "redraw": True},
                                "fromcurrent": True, "transition": {"duration": 300,
                                                                    "easing": "quadratic-in-out"}}],
                "label": "Play",
                "method": "animate"
            },
            {
                "args": [[None], {"frame": {"duration": 0, "redraw": True},
                                  "mode": "immediate",
                                  "transition": {"duration": 0}}],
                "label": "Pause",
                "method": "animate"
            }
        ],
        "direction": "left",
        "pad": {"r": 10, "t": 87},
        "showactive": False,
        "type": "buttons",
        "x": 0.1,
        "xanchor": "right",
        "y": 0,
        "yanchor": "top"
    }
]
sliders_dict = {
    "active": 0,
    "yanchor": "top",
    "xanchor": "left",
    "currentvalue": {
        "font": {"size": 20},
        "prefix": "Frame:",
        "visible": True,
        "xanchor": "right"
    },
    "transition": {"duration": 300, "easing": "cubic-in-out"},
    "pad": {"b": 10, "t": 50},
    "len": 0.9,
    "x": 0.1,
    "y": 0,
    "steps": []
}
with open(img_paths[0], "rb") as img_file:
    img_data = base64.b64encode(img_file.read()).decode()

img = Image.open(img_paths[0])
imwidth, imheight = img.size
# make data
image_dict = go.layout.Image(
            source="data:image/png;base64," + img_data,
            x=0,
            y=imheight,
            xref="x",
            yref="y",
            sizex=imwidth,
            sizey=imheight,
            opacity=1.0,
            visible=True,  
            layer="below"
        )
fig_dict["data"].append(image_dict)
   
for i, img_path in enumerate(img_paths[:5]):
    frame = {"data": [], "name": str(i+1)}

    with open(img_path, "rb") as img_file:
        img_data = base64.b64encode(img_file.read()).decode()

    img = Image.open(img_path)
    imwidth, imheight = img.size

    image_dict = go.layout.Image(
            source="data:image/png;base64," + img_data,
            x=0,
            y=imheight,
            xref="x",
            yref="y",
            sizex=imwidth,
            sizey=imheight,
            opacity=1.0,
            visible=(i==0),  
            layer="below"
        )
    frame["data"].append(image_dict)
    fig_dict["frames"].append(frame)

    slider_step = {
        "args": [
            [str(i+1)],
            {"frame": {"duration": 300, "redraw": True},
            "mode": "immediate",
            "transition": {"duration": 300},
            "visible": [(j == i) for j in range(5)]}  
        ],
        "label": f"Image {i+1}",
        "method": "animate"
    }
    # fig_dict["layout"]["sliders"][0]["steps"].append(slider_step)
    sliders_dict["steps"].append(slider_step)

import pdb;pdb.set_trace()
fig_dict["layout"]["sliders"] = [sliders_dict]

fig = go.Figure(fig_dict)

fig.update_layout(
    sliders=sliders,
    xaxis=dict(
        range=[0, 1600],  
        showgrid=False,
    ),
    yaxis=dict(
        range=[0, 900],  
        showgrid=False,
        scaleanchor='x',
    ),
)

output_folder = "/xxx/visualize/plotly"
output_filename = "multiphoto1.html"
output_path = os.path.join(output_folder, output_filename)
pio.write_html(fig, output_path)

This is the code I modified based on the code generated by the reference to generate 3D scatter plot. I thought that the form of custom image input is the same as loading 3D data, and the same data format can be used to control the change of 3D scatter plot by sliding the slider, but the image is actually different. The format of the image cannot be passed
I have tried another set of codes before, which is done through visible, but it can only display the first picture and the slider cannot be controlled


My plotly version is 2.24.1
This is what I generated with a set of code before, but this can’t complete the image switching

import plotly.graph_objects as go
import numpy as np
import base64
import os

from PIL import Image

folder_path = "/XXX/visualize/gt/multiview"
file_names = os.listdir(folder_path)
img_paths = [os.path.join(folder_path, file_name) for file_name in file_names if file_name.endswith((".png", ".jpg", ".jpeg"))]
fig = go.Figure()

visible = [False] * len(img_paths)  
visible[0] = True  

steps = []
for i in range(5):
    onestep = dict(
        method="update",
        args=[{"visible": [(j == i) for j in range(5)]}],
        label=f"Image {i+1}"
    )

    steps.append(onestep)

for i, img_path in enumerate(img_paths[:5]):
    with open(img_path, "rb") as img_file:
        img_data = base64.b64encode(img_file.read()).decode()

    img = Image.open(img_path)
    imwidth, imheight = img.size

    fig.add_layout_image(
        source="data:image/png;base64," + img_data,
        x=0,
        y=imheight,
        xref="x",
        yref="y",
        sizex=imwidth,
        sizey=imheight,
        opacity=1.0,
        visible=steps[0]["args"][0]["visible"][i],  
        # visible=(i == 3),
        layer="below"
    )

sliders = [dict(
    active=0,
    # currentvalue={"prefix": "Image: "},
    steps=steps
)]

fig.update_layout(
    sliders=sliders,
    xaxis=dict(
        range=[0, imwidth],  
        showgrid=False,
    ),
    yaxis=dict(
        range=[0, imheight],  
        showgrid=False,
        scaleanchor='x',
    ),
)

output_folder = "/XXX/visualize/plotly"
output_filename = "act0_vis3_0.html"
output_path = os.path.join(output_folder, output_filename)
fig.write_html(output_path)

I tried using the image, add_image and other APIs, as well as the add_layout_image API to add background images, but my requirement is not to add backgrounds, I just need to display images and use a slider to display the corresponding frames.

Traceback (most recent call last):
  File "/vividar/tools/plotly/multiphoto2.py", line 131, in <module>
    fig = go.Figure(
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/plotly/graph_objs/_figure.py", line 629, in __init__
    super(Figure, self).__init__(data, layout, frames, skip_invalid, **kwargs)
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/plotly/basedatatypes.py", line 592, in __init__
    self._frame_objs = self._frames_validator.validate_coerce(
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/_plotly_utils/basevalidators.py", line 2567, in validate_coerce
    res.append(self.data_class(v_el, skip_invalid=skip_invalid))
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/plotly/graph_objs/_frame.py", line 243, in __init__
    self["data"] = _v
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/plotly/basedatatypes.py", line 4869, in __setitem__
    self._set_array_prop(prop, value)
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/plotly/basedatatypes.py", line 5350, in _set_array_prop
    val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/_plotly_utils/basevalidators.py", line 2702, in validate_coerce
    self.raise_invalid_elements(invalid_els)
  File "/root/Software/anaconda3/envs/py38t19/lib/python3.8/site-packages/_plotly_utils/basevalidators.py", line 303, in raise_invalid_elements
    raise ValueError(
ValueError: 
    Invalid element(s) received for the 'data' property of frame
        Invalid elements include: [layout.Image({
    'layer': 'below',
    'opacity': 1.0,
    'sizex': 1600,
    'sizey': 900,
    'source': ('data:image/png;base64,/9j/4AAQ' ... 'KKACiiigAooooAKKKKACiiigD/2Q=='),
    'visible': True,
    'x': 0,
    'xref': 'x',
    'y': 900,
    'yref': 'y'
})]

    The 'data' property is a tuple of trace instances
    that may be specified as:
      - A list or tuple of trace instances
        (e.g. [Scatter(...), Bar(...)])
      - A single trace instance
        (e.g. Scatter(...), Bar(...), etc.)
      - A list or tuple of dicts of string/value properties where:
        - The 'type' property specifies the trace type
            One of: ['bar', 'barpolar', 'box', 'candlestick',
                     'carpet', 'choropleth', 'choroplethmapbox',
                     'cone', 'contour', 'contourcarpet',
                     'densitymapbox', 'funnel', 'funnelarea',
                     'heatmap', 'heatmapgl', 'histogram',
                     'histogram2d', 'histogram2dcontour', 'icicle',
                     'image', 'indicator', 'isosurface', 'mesh3d',
                     'ohlc', 'parcats', 'parcoords', 'pie',
                     'pointcloud', 'sankey', 'scatter',
                     'scatter3d', 'scattercarpet', 'scattergeo',
                     'scattergl', 'scattermapbox', 'scatterpolar',
                     'scatterpolargl', 'scattersmith',
                     'scatterternary', 'splom', 'streamtube',
                     'sunburst', 'surface', 'table', 'treemap',
                     'violin', 'volume', 'waterfall']

        - All remaining properties are passed to the constructor of
          the specified trace type

        (e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])

The image information obtained by the first code is as follows. The image type I defined before does not conform to the form of figure, but I see that there is image in the recommended type of figure.

Hi @qiuqc

Would a carousel from Dash Bootstrap Components work for you?

https://dash-bootstrap-components.opensource.faculty.ai/docs/components/carousel/

Thank you, AnnMarieW. I am not considering using dash for the time being. I am only going to use plotly’s api. I tried it yesterday, and the image can basically run according to my idea, but there is a new problem, that is, the image is upside down in the chart. If you use img_flipped = img.transpose(Image.FLIP_TOP_BOTTOM) to flip the image, the file size will be larger than the original file size generated by directly extracting the image. For example, the size of the HTML file generated by my original 5 images is only 4.9MB, but after flipping, the size has reached 39.9MB, and after opening, a white screen appears.

folder_path = "/xxx/visualize/gt/multiview"
file_names = os.listdir(folder_path)
img_paths = [os.path.join(folder_path, file_name) for file_name in file_names if file_name.endswith((".png", ".jpg", ".jpeg"))]
# make figure
fig_dict = {
    "data": [],
    "layout": {},
    "frames": []
}

# fill in most of layout
fig_dict["layout"]["hovermode"] = "closest"
fig_dict["layout"]["updatemenus"] = [
    {
        "buttons": [
            {
                "args": [None, {"frame": {"duration": 500, "redraw": True},
                                "fromcurrent": True, "transition": {"duration": 300,
                                                                    "easing": "quadratic-in-out"}}],
                "label": "Play",
                "method": "animate"
            },
            {
                "args": [[None], {"frame": {"duration": 0, "redraw": True},
                                  "mode": "immediate",
                                  "transition": {"duration": 0}}],
                "label": "Pause",
                "method": "animate"
            }
        ],
        "direction": "left",
        "pad": {"r": 10, "t": 87},
        "showactive": False,
        "type": "buttons",
        "x": 0.1,
        "xanchor": "right",
        "y": 0,
        "yanchor": "top"
    }
]
sliders_dict = {
    "active": 0,
    "yanchor": "top",
    "xanchor": "left",
    "currentvalue": {
        "font": {"size": 20},
        "prefix": "Frame:",
        "visible": True,
        "xanchor": "right"
    },
    "transition": {"duration": 300, "easing": "cubic-in-out"},
    "pad": {"b": 10, "t": 50},
    "len": 0.9,
    "x": 0.1,
    "y": 0,
    "steps": []
}
with open(img_paths[0], "rb") as img_file:
    img_data = base64.b64encode(img_file.read()).decode()

img = Image.open(img_paths[0])
imwidth, imheight = img.size
import pdb;pdb.set_trace()
# make data
image_dict = go.Image(
            source="data:image/png;base64," + img_data,
            x0=0,
            y0=0,
            xaxis="x",
            yaxis="y",
            # dx=imwidth,
            # dy=imheight,
            opacity=1.0,
            visible=True,  
        )
fig_dict["data"].append(image_dict)
   
for i, img_path in enumerate(img_paths[:5]):
    frame = {"data": [], "name": str(i+1)}

    with open(img_path, "rb") as img_file:
        img_data = base64.b64encode(img_file.read()).decode()

    img = Image.open(img_path)
    imwidth, imheight = img.size
    import pdb;pdb.set_trace()

    image_dict = go.Image(
            source="data:image/png;base64," + img_data,
            x0=0,
            y0=0,
            xaxis="x",
            yaxis="y",
            # dx=imwidth,
            # dy=imheight,
            opacity=1.0,
        )
    frame["data"].append(image_dict)
    fig_dict["frames"].append(frame)

    slider_step = {
        "args": [
            [str(i+1)],
            {"frame": {"duration": 300, "redraw": True},
            "mode": "immediate",
            "transition": {"duration": 300},}  
        ],
        "label": f"Image {i+1}",
        "method": "animate"
    }
    sliders_dict["steps"].append(slider_step)

fig_dict["layout"]["sliders"] = [sliders_dict]
fig = go.Figure(fig_dict)

fig.update_layout(
    sliders=sliders_dict,
    xaxis=dict(
        range=[0, 1600],  
        showgrid=False,
    ),
    yaxis=dict(
        range=[0, 900],  
        showgrid=False,
        scaleanchor='x',
        autorange=True
    ),
)