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
563 stars 85 forks source link

Containing the canvas in beta columns #38

Closed tomvars closed 3 years ago

tomvars commented 3 years ago

Hi,

Thank you for creating this fantastic tool. I'm trying to build a use case for selecting ROIs in 3D radiological images in a Streamlit dashboard.

How could I use the st.beta_columns API to put 3 separate drawable canvases side by side?

Best, Tom

andfanilo commented 3 years ago

Hello @tomvars !

Something like

import streamlit as st
from streamlit_drawable_canvas import st_canvas

col1, col2, col3 = st.beta_columns(3)

with col1:
    canvas_image1 = st_canvas(
        fill_color = "rgba(255, 165, 0, 0.3)", 
        stroke_width = 2,
        stroke_color = '#e00',
        drawing_mode = "line",
        width=100,
        key = "1",
    )
with col2:
    canvas_image2 = st_canvas(
        fill_color = "rgba(255, 165, 0, 0.3)", 
        stroke_width = 2,
        stroke_color = '#e00',
        drawing_mode = "line",
        width=100,
        key = "2",
    )
with col3:
    canvas_image3 = st_canvas(
        fill_color = "rgba(255, 165, 0, 0.3)", 
        stroke_width = 2,
        stroke_color = '#e00',
        drawing_mode = "line",
        width=100,
        key = "3",
    )
if canvas_image1.image_data is not None:
    st.image(canvas_image1.image_data)

would do?

Do take note that for now canvas is not responsive, you'll have to supply fixed width values. I just opened #39 for this, I don't know how exactly I'll manage this yet. Would responsive canvas be a need for you, or do you prefer fixed dimensions anyway?

(also can't do anything about the alignement of canvas in columns for now since this is not yet in Streamlit, so your best bet would be to fiddle with columns yourself by adding empty columns in between to space the canvases manually)

tomvars commented 3 years ago

Thank you, that worked perfectly.

Fixed dimensions is fine for my use case. I have a separate issue, i'll include it here for now but let me know if you'd prefer me to close this issue and reopen a new one.

I'm using sliders to travel up and down an image along a particular dimension. I'm trying to separately annotate each slice which is a new background image and have the app remember what had been previously annotated on a particular slice when returning to it. My attempt was to store the json data in a dict indexed by the slice number and use the initial_drawing argument to populate the previously annotated data. However the initial drawing does not seem to be working as expected.

here is a reduced example:

import streamlit as st
from streamlit import components
import numpy as np
import pandas as pd
import nibabel as nib
import sys
from PIL import Image
from matplotlib import cm
from streamlit_drawable_canvas import st_canvas
from collections import defaultdict

json_results = {idx: {'version': "3.6.3", "objects": []} for idx in range(256)}

def main():
    st.title("Basic viewer")
    img_file_buffer = None
    img_file_buffer = st.file_uploader("Upload a T1 niftii", type=["nii", "nii.gz"])

    if img_file_buffer is None:
        st.stop()
    else:
        with open(img_file_buffer.name, "wb") as f:
            f.write(img_file_buffer.getbuffer())
    img = nib.load(img_file_buffer.name).get_data()
    z_idx = st.slider('z', min_value=0, max_value=img.shape[2],
                  value=0)
    axial_slice = img[:, :, z_idx, ...]
    # Specify canvas parameters in application
    stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3)
    stroke_color = st.sidebar.color_picker("Stroke color hex: ")
    bg_color = st.sidebar.color_picker("Background color hex: ", "#eee")
    drawing_mode = st.sidebar.selectbox(
        "Drawing tool:", ("freedraw", "line", "rect", "circle", "transform")
    )
    realtime_update = st.sidebar.checkbox("Update in realtime", True)

    # Create a canvas component
    axial_slice = axial_slice/axial_slice.max() # normalize
    im = Image.fromarray(np.uint8(cm.Greys_r(np.rot90(np.fliplr(slices[2]), k=3))*255))
    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="" if im else bg_color,
        background_image=im,
        update_streamlit=realtime_update,
        height=slices[2].shape[0],
        width=slices[2].shape[1],
        drawing_mode=drawing_mode,
        key="canvas" + "2" + f"_{z_idx}",
        initial_drawing=None if not json_results[z_idx]["objects"] else json_results[z_idx]
    )

    if canvas_result.json_data is not None:
        if canvas_result.json_data["objects"] and not json_results[z_idx]["objects"]:
            json_results[z_idx] = canvas_result.json_data

    st.write(json_results)
    if canvas_result.json_data is not None:
        st.dataframe(pd.json_normalize(canvas_result.json_data["objects"]))

Might be something to do with the keyargument.

andfanilo commented 3 years ago

Hmmm without testing, I'd say json_results is reset at every canvas interaction (since at every canvas interaction to rerun the script from top to bottom, you reinitialize json_results every time right?). Try st.json(json_results) at the very end and see if values are correctly populated.

If not, you'll need to store your json_results in state using the infamous "SessionState" gist (https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92), which is preserved across runs (until the official cache is released soon ;) ...)

In case you need examples, there are plenty on the forums like https://discuss.streamlit.io/t/multiselect-based-on-user-input/4188/2 for more info ;)

Hope this helps! I'll test your code tomorrow. Fanilo

andfanilo commented 3 years ago

Hi @tomvars,

I'll be closing this for now, feel free to reopen if you're working on this again!

Fanilo