Displaying local images inside a streamlit dataframe

Hi, I’m pretty new to Streamlit. My goal is to build a simple database. Guided by [this](# Get dataframe row-selections from users - Streamlit Docs) example, I was able to customize the pandas dataframe and streamlit dataframe to my needs.

Each item in the database is associated with an image, which I would like to display in the dataframe. The images are stored locally as PNGs. Reading some other topics, I managed to get this code:

import pandas as pd
import streamlit as st

from pathlib import Path
from faker import Faker
from PIL import Image
from io import BytesIO
import base64

st.set_page_config(layout="wide")  # https://discuss.streamlit.io/t/how-to-increase-the-width-of-web-page/7697

map_paths = [f"/home/my.name/Pictures/db_trial/maps/map{i}.png" for i in range(1, 7)]
for x in map_paths:
    assert Path(x).exists()


def get_image_from_disk(path_to_image):
    return pure_pil_alpha_to_color_v2(Image.open(path_to_image))


def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)):
    """Alpha composite an RGBA Image with a specified color.

    Simpler, faster version than the solutions above.

    Source: http://stackoverflow.com/a/9459208/284318

    Keyword Arguments:
    image -- PIL RGBA Image object
    color -- Tuple r, g, b (default 255, 255, 255)

    """
    image.load()  # needed for split()
    background = Image.new('RGB', image.size, color)
    background.paste(image, mask=image.split()[3])  # 3 is the alpha channel
    return background


def image_to_base64(img):
    if img:
        with BytesIO() as buffer:
            img.save(buffer, "png")
            return base64.b64encode(buffer.getvalue()).decode()


@st.cache_data
def get_profile_dataset(number_of_items: int = 20, seed: int = 0) -> pd.DataFrame:
    new_data = []

    fake = Faker()
    np.random.seed(seed)
    Faker.seed(seed)

    for i in range(number_of_items):
        profile = fake.profile()
        new_data.append(
            {
                "name": profile["name"],
                "map": image_to_base64(get_image_from_disk(map_paths[np.random.randint(0, len(map_paths))])),  # pick a random image from disk. image is not displayed in the dataframe
                # "map": "https://i.imgur.com/fH2LHvo.png",  # this image is displayed correctly in the dataframe
                "revision": ("A", "B", "C")[np.random.randint(0, 2 + 1)],
                "sensor": ("None", "GaAs", "CdTe")[np.random.randint(0, 2 + 1)],
                "frame": ("None", "small", "big")[np.random.randint(0, 2 + 1)],
                "bad_pixels": np.random.randint(0, 200 + 1, size=4),  # one entry per threshold (should be percentage)
                "dac_failures": np.random.randint(0, 10 + 1),
            }
        )

    profile_df = pd.DataFrame(new_data)
    return profile_df


column_configuration = {
    "name": st.column_config.TextColumn(
        "Name", help="The name of the module", max_chars=100, width="small"
    ),
    "map": st.column_config.ImageColumn(
        "Map", help="Module map", width="small"
    ),
    "revision": st.column_config.TextColumn(
        "Rev", help="ASIC Revision", max_chars=3, width="small"
    ),
    "sensor": st.column_config.TextColumn(
        "Sensor", help="The type of sensor", max_chars=15, width="small"
    ),
    "frame": st.column_config.TextColumn(
        "Frame", help="The type of frame", max_chars=15, width="small"
    ),
    "bad_pixels": st.column_config.BarChartColumn(
        "Bad Pixels",
        help="The number of bad pixels per threshold",
        width="small",
        y_min=0,
        y_max=3,
    ),
    "dac_failures": st.column_config.NumberColumn(
        "DAC Failures",
        help="The number of failed DACs",
        width="small",
    )
}

select, compare = st.tabs(["Select Modules", "Compare selected"])

with select:
    st.header("All Modules")

    df = get_profile_dataset()

    event = st.dataframe(
        df,
        column_config=column_configuration,
        use_container_width=True,
        hide_index=True,
        on_select="rerun",
        selection_mode="multi-row",
    )

    st.header("Selected Modules")
    modules = event.selection.rows
    filtered_df = df.iloc[modules]
    st.dataframe(
        filtered_df,
        column_config=column_configuration,
        use_container_width=True,
    )

with compare:
    # comparison can be improved a lot
    bad_pixel_df = {}
    for module in modules:
        bad_pixel_df[df.iloc[module]["name"]] = df.iloc[module]["bad_pixels"]
    bad_pixel_df = pd.DataFrame(bad_pixel_df)

    dac_failures_df = {}
    for module in modules:
        dac_failures_df[df.iloc[module]["name"]] = df.iloc[module]["dac_failures"]
    dac_failures_df = pd.DataFrame(dac_failures_df, index=[0])

    if len(modules) > 0:
        st.header("Bad Pixel comparison")
        st.bar_chart(bad_pixel_df)
        st.header("DAC Failures comparison")
        st.bar_chart(dac_failures_df)

    else:
        st.markdown("No modules selected.")

For each item my code adds the output of image_to_base64() to the pandas dataframe entry. The dataframe is passed to st.dataframe. But the images don’t show up. There is no error message:

The “Maps” column should contain the images loaded from disk. Instead it is empty. What am I doing wrong? If I use the random imgur URL, the image of a chair appears in the “Maps” column, therefore I assume that the dataframe itself is configured correctly?

1 Like

Hi @Christian8, welcome to the forum!

If you check out the example from st.column_config.ImageColumn - Streamlit Docs, you’ll see that for base64 images, you need to include data:image/png;base64, before the actual base64 contents. If you modify your image_to_base64 function to do that, it should work fine

def image_to_base64(img):
    if img:
        with BytesIO() as buffer:
            img.save(buffer, "png")
            raw_base64 = base64.b64encode(buffer.getvalue()).decode()
            return f"data:image/png;base64,{raw_base64}"

Thanks a lot! Turns out I wasn’t understanding the documentation as well as I thought I did, shame on me.

Small follow-up question: Is there a way to control the size of the images inside the dataframe, e.g. set a minimum and maximum height+width?

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.

No, not that I know of – I think there’s just the default size that gets used automatically.