andfanilo / streamlit-drawable-canvas

Do you like Quick, Draw? Well what if you could train/predict doodles drawn inside Streamlit? Also draws lines, circles and boxes over background images for annotation.
https://drawable-canvas.streamlit.app/
MIT License
576 stars 88 forks source link

st_canvas displays cropped background image #61

Closed hello-fri-end closed 2 years ago

hello-fri-end commented 2 years ago

Hi! Thanks for the super cool package. As the title says st_canvas displays a cropped background image even though the heightand width of the canvas are set acc. to the image dimensions.

Here's my code:

    canvas_result = st_canvas(
            stroke_color='#fff',
            stroke_width=stroke_width,
            background_color='#000',
            background_image=image.copy(),
            height = image.size[1],
            width = image.size[0],
            update_streamlit=realtime_update,
            drawing_mode = drawing_mode,
            key = "canvas",
            )

Am I doing something wrong?

andfanilo commented 2 years ago

Hey @hello-fri-end !

I'm not near my personal computer so can't test :/ but this is odd. I've actually never tried passing the image dimensions as height and width, but you should not have to because I resize the image to fill the entire canvas.

Have a nice day, Fanilo

hello-fri-end commented 2 years ago

Hey @hello-fri-end !

I'm not near my personal computer so can't test :/ but this is odd. I've actually never tried passing the image dimensions as height and width, but you should not have to because I resize the image to fill the entire canvas.

  • What happens if you remove the height/width arguments?

Here's the entire code:

canvas_result = st_canvas(
            stroke_color='#fff',
            stroke_width=stroke_width,
            background_color='#000',
            background_image=image.copy(),
            height = image.size[1],
            width = image.size[0],
            update_streamlit=realtime_update,
            drawing_mode = drawing_mode,
            key = "canvas",
            )
if st.button("INPAINT"):
    image = np.array(image, dtype = 'uint8')
    mask = np.array(canvas_result.image_data, dtype = 'uint8')
    mask = cv2.cvtColor(mask[:, :, :3], cv2.COLOR_RGB2GRAY)
    st.image(mask)
    res1 = cv2.inpaint(src = image, inpaintMask = mask, inpaintRadius = inpaintRadius, flags = cv2.INPAINT_TELEA)
    res2 = cv2.inpaint(src = image, inpaintMask = mask, inpaintRadius = inpaintRadius, flags = cv2.INPAINT_NS)`

When I remove the height/width arguments, the function cv2.inpaint complains that the mask & image are not of the same size and the canvas size looks smaller.

  • Did I maybe wrongly mixed width and height too, what happens if you invert the 2 dimensions?

No, on inverting the image looks weirdly stretched.

  1. Here's the original image:
  2. Here's how it looks with height & width as image dimensions.
  3. Here's how it looks on inverting the height & width.
  4. Here's how it looks on removing the height & width parameters.
hello-fri-end commented 2 years ago

Moreover, when height/width parameters are removed & I print the shape of mask, it comes out as (400,600)

andfanilo commented 2 years ago

Moreover, when height/width parameters are removed & I print the shape of mask, it comes out as (400,600)

Yeah those are the dimensions of the canvas by default, and the mask you get out from the canvas are sized by canvas size so you'll need to reshape.

As the title says st_canvas displays a cropped background image even though the height and width of the canvas are set acc. to the image dimensions.

Ah I get it now :/ the canvas is cropped by the size of the Streamlit column. The canvas is not responsive so if the canvas width is larger than the container, the canvas will be cropped by the container. If you put your app in wide mode and your picture is not 2160px wide, you won't have the crop problem.

My only good suggestion is to actually downsize the image for now :/

hello-fri-end commented 2 years ago

Ah I get it now :/ the canvas is cropped by the size of the Streamlit column

That's strange since st.image displays the full image :/

My only good suggestion is to actually downsize the image for now :/

Yup! That seems like my only option right now. Thanks :)

andfanilo commented 2 years ago

st.image resizes to the size of the column, but not my HTML canvas component because I've hardcoded the dimensions and it's not responsive for now, the joy of implementing canvas by hand :/ ...

Hope you get away with downsizing a bit!

andfanilo commented 2 years ago

Closing in favor of #39

Fionn-Aldric commented 2 years ago

Hi everyone,

I found a stupid way to achieve responsive canvas size. I use streamlit_javascript component to read window.innerWidth and pass it to canvas. But it still has some problems, just for reference. Here's my code snippet.

import streamlit as st
from streamlit_javascript import st_javascript

import pandas as pd
from PIL import Image
from streamlit_drawable_canvas import st_canvas

st.set_page_config(layout='wide')

# Specify canvas parameters in application
drawing_mode = st.sidebar.selectbox(
    "Drawing tool:", ("polygon", "freedraw", "line", "rect", "circle", "transform", "point")
)

stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3)
if drawing_mode == 'point':
    point_display_radius = st.sidebar.slider("Point display radius: ", 1, 25, 3)
stroke_color = st.sidebar.color_picker("Stroke color hex: ")
bg_color = st.sidebar.color_picker("Background color hex: ", "#eee")
bg_image = st.sidebar.file_uploader("Background image:", type=["png", "jpg"])

realtime_update = st.sidebar.checkbox("Update in realtime", True)

if bg_image:
    img = Image.open(bg_image)
    w, h = img.size
else:
    w, h = 100, 100

st.subheader("Javascript API call")

option = st.radio('', ['Default Width', 'Update Canvas Width'])

if option == 'Default Width':
    inner_width = 100
elif option == 'Update Canvas Width':
    inner_width = st_javascript("""await fetch("http://localhost:8501/").then(function(response) {
        return window.innerWidth;
    }) """)

st.markdown(f"Inner width was: {inner_width}")

canvas_width = inner_width
canvas_height = h * (canvas_width / w)

# Create a canvas component
canvas_result = st_canvas(
    fill_color="rgba(255, 165, 0, 0.3)",  # Fixed fill color with some opacity
    stroke_width=stroke_width,
    stroke_color=stroke_color,
    background_color=bg_color,
    background_image=Image.open(bg_image) if bg_image else None,
    update_streamlit=realtime_update,
    height=canvas_height,
    width=canvas_width,
    drawing_mode=drawing_mode,
    point_display_radius=point_display_radius if drawing_mode == 'point' else 0,
)

# Do something interesting with the image data and paths
if canvas_result.image_data is not None:
    st.image(canvas_result.image_data, width=inner_width)

if canvas_result.json_data is not None:
    objects = pd.json_normalize(canvas_result.json_data["objects"]) # need to convert obj to str because PyArrow
    for col in objects.select_dtypes(include=['object']).columns:
        objects[col] = objects[col].astype("str")
    st.dataframe(objects)
andfanilo commented 2 years ago

Oh that's super nice, thanks for sharing :)

MacBook-Pro-gala commented 2 years ago

How to make a high-resolution background scroll horizontally across a canvas. Because I want to get the actual coordinates of the rectangle on the original image