├── 3bf863cc.pub ├── 7fa2af80.pub ├── Dockerfile ├── README.md ├── demo └── demo.gif ├── license ├── requirements.txt ├── sam_st.py ├── streamlit_dc ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── e2e │ ├── app_to_test.py │ ├── cypress.json │ ├── cypress │ │ ├── integration │ │ │ └── streamlit_canvas_spec.js │ │ └── plugins │ │ │ └── index.js │ └── package.json ├── img │ └── demo.gif ├── setup.py └── streamlit_drawable_canvas │ ├── __init__.py │ └── frontend │ ├── .env │ ├── .gitignore │ ├── .prettierrc │ ├── module.rules │ ├── package.json │ ├── package.json.bak │ ├── pnpm-lock.yaml │ ├── public │ └── index.html │ ├── src │ ├── DrawableCanvas.tsx │ ├── DrawableCanvasState.tsx │ ├── components │ │ ├── CanvasToolbar.module.css │ │ ├── CanvasToolbar.tsx │ │ └── UpdateStreamlit.tsx │ ├── img │ │ ├── bin.png │ │ ├── download.png │ │ └── undo.png │ ├── index.css │ ├── index.tsx │ ├── lib │ │ ├── circle.ts │ │ ├── fabrictool.ts │ │ ├── freedraw.ts │ │ ├── index.ts │ │ ├── line.ts │ │ ├── point.ts │ │ ├── polygon.ts │ │ ├── rect.ts │ │ └── transform.ts │ └── react-app-env.d.ts │ └── tsconfig.json └── util.py /3bf863cc.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v2.0.22 (GNU/Linux) 3 | 4 | mQINBGJYmlEBEAC6nJmeqByeReM+MSy4palACCnfOg4pOxffrrkldxz4jrDOZNK4 5 | q8KG+ZbXrkdP0e9qTFRvZzN+A6Jw3ySfoiKXRBw5l2Zp81AYkghV641OpWNjZOyL 6 | syKEtST9LR1ttHv1ZI71pj8NVG/EnpimZPOblEJ1OpibJJCXLrbn+qcJ8JNuGTSK 7 | 6v2aLBmhR8VR/aSJpmkg7fFjcGklweTI8+Ibj72HuY9JRD/+dtUoSh7z037mWo56 8 | ee02lPFRD0pHOEAlLSXxFO/SDqRVMhcgHk0a8roCF+9h5Ni7ZUyxlGK/uHkqN7ED 9 | /U/ATpGKgvk4t23eTpdRC8FXAlBZQyf/xnhQXsyF/z7+RV5CL0o1zk1LKgo+5K32 10 | 5ka5uZb6JSIrEPUaCPEMXu6EEY8zSFnCrRS/Vjkfvc9ViYZWzJ387WTjAhMdS7wd 11 | PmdDWw2ASGUP4FrfCireSZiFX+ZAOspKpZdh0P5iR5XSx14XDt3jNK2EQQboaJAD 12 | uqksItatOEYNu4JsCbc24roJvJtGhpjTnq1/dyoy6K433afU0DS2ZPLthLpGqeyK 13 | MKNY7a2WjxhRmCSu5Zok/fGKcO62XF8a3eSj4NzCRv8LM6mG1Oekz6Zz+tdxHg19 14 | ufHO0et7AKE5q+5VjE438Xpl4UWbM/Voj6VPJ9uzywDcnZXpeOqeTQh2pQARAQAB 15 | tCBjdWRhdG9vbHMgPGN1ZGF0b29sc0BudmlkaWEuY29tPokCOQQTAQIAIwUCYlia 16 | UQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEKS0aZY7+GPM1y4QALKh 17 | BqSozrYbe341Qu7SyxHQgjRCGi4YhI3bHCMj5F6vEOHnwiFH6YmFkxCYtqcGjca6 18 | iw7cCYMow/hgKLAPwkwSJ84EYpGLWx62+20rMM4OuZwauSUcY/kE2WgnQ74zbh3+ 19 | MHs56zntJFfJ9G+NYidvwDWeZn5HIzR4CtxaxRgpiykg0s3ps6X0U+vuVcLnutBF 20 | 7r81astvlVQERFbce/6KqHK+yj843Qrhb3JEolUoOETK06nD25bVtnAxe0QEyA90 21 | 9MpRNLfR6BdjPpxqhphDcMOhJfyubAroQUxG/7S+Yw+mtEqHrL/dz9iEYqodYiSo 22 | zfi0b+HFI59sRkTfOBDBwb3kcARExwnvLJmqijiVqWkoJ3H67oA0XJN2nelucw+A 23 | Hb+Jt9BWjyzKWlLFDnVHdGicyRJ0I8yqi32w8hGeXmu3tU58VWJrkXEXadBftmci 24 | pemb6oZ/r5SCkW6kxr2PsNWcJoebUdynyOQGbVwpMtJAnjOYp0ObKOANbcIg+tsi 25 | kyCIO5TiY3ADbBDPCeZK8xdcugXoW5WFwACGC0z+Cn0mtw8z3VGIPAMSCYmLusgW 26 | t2+EpikwrP2inNp5Pc+YdczRAsa4s30Jpyv/UHEG5P9GKnvofaxJgnU56lJIRPzF 27 | iCUGy6cVI0Fq777X/ME1K6A/bzZ4vRYNx8rUmVE5 28 | =DO7z 29 | -----END PGP PUBLIC KEY BLOCK----- 30 | -------------------------------------------------------------------------------- /7fa2af80.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1 3 | 4 | mQINBFdtt4UBEAC8FDSWMR07GJZ265giLn7kLF+EsJCWESUq6Cd13QN0JQ/tLibi 5 | QlW4ZjeOnEH9VPlqh/mKqNMG4SwRt8S+GHpePMQrr0aOkiRGfCclnAWIZURSAP+t 6 | PLelCt43fkw1BBTopd/0oOzO8kHu8j8WU4A8GHxqghfFWPv54FQs2iaZ2eWR7a6d 7 | 79IJrbDKaVCCiQrkhCM8m648pNKHhuoJ9cQXFV+uvwkpfmKWGQ4ultxlOyjLHJLF 8 | vuML2RuAO9IxbdZjzeYNN+T+wjFIBVcPnwEO+WrYgvGkT4r9aqVqTeg3EPb7QclV 9 | sKBVJdxk4jZl0y22HAWqScVi6SJ15uK9pXxywDZkbpuRBWx4ThWiGe/FiUa2igi9 10 | /SIvqN2TBY0g18sRTrylVr1wE1UGa/y7nDx6PoGCP1frBt8YUYt3pkM8Xvb2CRxx 11 | CyWwmuFEQHC6jCEWf7FnoBHBYQwTVGNrU0vkuIeDrm+ZAcv8wx+ie1hlFhqCCJnf 12 | jqeQ0/zA9RPmCPOkLyTdSsNZtlxxk7bzCdTdFFKzBjGTR7Gz3SMSp23d11eIyRiF 13 | HQsp2v0SvnPJ6OcgB95Hmo544vi3RuoVfovtDOdfSBCRxP+GhhxkKSrTleQjD0/r 14 | CGkdG2Kox3m9YllAsvZchLXlS7bZV9mGRF61mVMjF3HJRUQfBBm89VPQ+QARAQAB 15 | tCBjdWRhdG9vbHMgPGN1ZGF0b29sc0BudmlkaWEuY29tPokCNwQTAQgAIQUCV223 16 | hQIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRD2D0s9f6KvgNArEAChnfcW 17 | rYItgt7xXXubT6E+KpJyJ0RPrXf51S2mhciFbjDl+3EXRMRjOutVmgWYPWUUZaKR 18 | 8Iez3Lz4BRmwYOWBLtdnOLbKoSsQUX95rnPFjfly/DFLfjKxz4NRBmh4r4/rCYWm 19 | 2hmnXmOAi8kV7fqx3g5XMpJ//N6+T8ctEol2iZ82GrXjadcRWE4rAe7UyuEzJ74y 20 | 6ZKIzk5ijdgEKtcaBhzEWvoV5Pr9nkn7ByGsdehKR/gNnjPMYXrklSHGfphJIsS2 21 | S32lMk/kuRjihBcWcYBXIPEQ7CV+PNW2TlkZj/YqTg637sZHwkhcjcNzxeqKvRYG 22 | 8V7Ju5hTDxL1UQBmgDS3cRx1lw7tYRG5bS67tbC2dc/CpPkG5agiZ/WyoHQDnn4r 23 | 1fRuOFx694QR6+0rAP6171xEEoNAPaH7gdJdhWKiYiJD0T2EEbW7wBUi/EupeKRv 24 | kR12R1jUa1mlpxNtWQxJ7qp98T9+DmkxI1XDmWx0/g4ryuicwLDSqoPgNcRNdSQb 25 | b8YfTqrkqaDdYzwLr/n0YKW3cYIvIeisV0WxRjb6OP7oAlAtaAhImlIc//51qNO7 26 | /WAud6qMtnhFoZayR/BzLKqnCioN5GYr9BAKskpPHe9cDKVS3fg+Qvc1sNJID+jf 27 | k52PqyW24Qsr0A9+5zQyE4tH9dfv120gj9avmg== 28 | =0nKc 29 | -----END PGP PUBLIC KEY BLOCK----- 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | WORKDIR /home 6 | 7 | COPY 7fa2af80.pub /tmp/ 8 | COPY 3bf863cc.pub /tmp/ 9 | 10 | RUN apt-key add /tmp/7fa2af80.pub &&\ 11 | apt-key add /tmp/3bf863cc.pub 12 | 13 | RUN echo 'deb http://mirrors.cloud.tencent.com/ubuntu/ focal main restricted universe multiverse\n\ 14 | deb http://mirrors.cloud.tencent.com/ubuntu/ focal-security main restricted universe multiverse\n\ 15 | deb http://mirrors.cloud.tencent.com/ubuntu/ focal-updates main restricted universe multiverse\n\ 16 | #deb http://mirrors.cloud.tencent.com/ubuntu/ focal-proposed main restricted universe multiverse\n\ 17 | #deb http://mirrors.cloud.tencent.com/ubuntu/ focal-backports main restricted universe multiverse\n\ 18 | deb-src http://mirrors.cloud.tencent.com/ubuntu/ focal main restricted universe multiverse\n\ 19 | deb-src http://mirrors.cloud.tencent.com/ubuntu/ focal-security main restricted universe multiverse\n\ 20 | deb-src http://mirrors.cloud.tencent.com/ubuntu/ focal-updates main restricted universe multiverse\n'\ 21 | > /etc/apt/sources.list && \ 22 | apt-get update --fix-missing && DEBIAN_FRONTEND=noninteractive TZ=Asia/Shanghai && apt-get install -y --no-install-recommends \ 23 | git \ 24 | npm \ 25 | wget \ 26 | python3 \ 27 | python3-dev \ 28 | python3-pip \ 29 | python3-opencv && \ 30 | apt-get autoremove && \ 31 | apt-get clean && \ 32 | rm -rf /var/lib/apt/lists/* && \ 33 | pip3 install --no-cache-dir \ 34 | pip \ 35 | setuptools && \ 36 | python3 -m pip install --upgrade pip 37 | 38 | 39 | RUN git clone http://deepmaterial.work/git/luziqing/segment_anything_streamlit.git && cd segment_anything_streamlit && \ 40 | pip install --no-cache-dir git+https://github.com/facebookresearch/segment-anything.git -i https://pypi.tuna.tsinghua.edu.cn/simple &&\ 41 | pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple &&\ 42 | cd streamlit_dc/streamlit_drawable_canvas/frontend && npm install && npm run build && cd ../../ && pip install -e . && \ 43 | cd /home/segment_anything_streamlit && mkdir checkpoint && \ 44 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth -O checkpoint/sam_vit_b_01ec64.pth && \ 45 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth -O checkpoint/sam_vit_l_0b3195.pth && \ 46 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth -O checkpoint/sam_vit_h_4b8939.pth 47 | 48 | 49 | WORKDIR /home/segment_anything_streamlit 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Segment anything-streamlit webui 2 | ![demo](./demo/demo.gif) 3 | 4 | This is a streamlit web interface for the [Segment Anything](https://github.com/facebookresearch/segment-anything). 5 | 6 | # 1. install Modified streamlit_drawable_canvas 7 | We modified [streamlit-drawable-canvas](https://github.com/andfanilo/streamlit-drawable-canvas) to add Left and Right Click Functions 8 | 9 | If you have installed streamlit_drawable_canvas, uninstall it 10 | ```shell 11 | pip uninstall streamlit-drawable-canvas 12 | ``` 13 | Install our version,make sure you have installed npm 14 | ```shell 15 | cd streamlit_dc/streamlit_drawable_canvas/frontend 16 | npm install 17 | npm run build 18 | cd streamlit_dc/ 19 | pip install -e . 20 | cd ../ 21 | ``` 22 | 23 | # 2. install dependencies and get checkpoints 24 | ```shell 25 | pip install --no-cache-dir git+https://github.com/facebookresearch/segment-anything.git 26 | pip install -r requirements.txt 27 | 28 | mkdir checkpoint 29 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth -O checkpoint/sam_vit_b_01ec64.pth 30 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth -O checkpoint/sam_vit_l_0b3195.pth 31 | wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth -O checkpoint/sam_vit_h_4b8939.pth 32 | ``` 33 | 34 | # 3. Run 35 | ```shell 36 | streamlit run sam_st.py 37 | ``` 38 | 39 | # Docker 40 | ```shell 41 | docker build -t sam_st . 42 | docker run --gpus "device=0" -itd -p 84:8501 --name sam_st sam_st:latest bash -c 'streamlit run sam_st.py' 43 | ``` -------------------------------------------------------------------------------- /demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarkMi/segment_anything_streamlit_webui/3991206a625d2e8f04e12d3ff038710e22b3353f/demo/demo.gif -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 LarkMi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.20.0 2 | torch==2.0.0 3 | Pillow==9.2.0 4 | numpy==1.24.2 5 | pandas==1.5.3 6 | distinctipy==1.2.2 7 | torchvision 8 | -------------------------------------------------------------------------------- /sam_st.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ["CUDA_VISIBLE_DEVICES"] = '1' 3 | import streamlit as st 4 | import torch 5 | from PIL import Image 6 | import numpy as np 7 | from streamlit_drawable_canvas import st_canvas 8 | import pandas as pd 9 | import random 10 | from io import BytesIO 11 | from util import model_predict_click, model_predict_box, model_predict_everything, show_click, show_everything, get_color 12 | 13 | 14 | 15 | def click(container_width,height,scale,radius_width,show_mask,model,im): 16 | for each in ['color_change_point_box','input_masks_color_box']: 17 | if each in st.session_state:st.session_state.pop(each) 18 | canvas_result = st_canvas( 19 | fill_color="rgba(255, 255, 0, 0.8)", 20 | background_image = st.session_state['im'], 21 | drawing_mode='point', 22 | width = container_width, 23 | height = height * scale, 24 | point_display_radius = radius_width, 25 | stroke_width=2, 26 | update_streamlit=True, 27 | key="click",) 28 | if not show_mask: 29 | im = Image.fromarray(im).convert("RGB") 30 | rerun = False 31 | if im != st.session_state['im']: 32 | rerun = True 33 | st.session_state['im'] = im 34 | if rerun: 35 | st.experimental_rerun() 36 | elif canvas_result.json_data is not None: 37 | color_change_point = st.button('Save color') 38 | df = pd.json_normalize(canvas_result.json_data["objects"]) 39 | if len(df) == 0: 40 | st.session_state.clear() 41 | if 'canvas_result' not in st.session_state: 42 | st.session_state['canvas_result'] = len(df) 43 | st.experimental_rerun() 44 | elif len(df) != st.session_state['canvas_result']: 45 | st.session_state['canvas_result'] = len(df) 46 | st.experimental_rerun() 47 | return 48 | 49 | df["center_x"] = df["left"] 50 | df["center_y"] = df["top"] 51 | input_points = [] 52 | input_labels = [] 53 | 54 | for _, row in df.iterrows(): 55 | x, y = row["center_x"] + 5, row["center_y"] 56 | x = int(x/scale) 57 | y = int(y/scale) 58 | input_points.append([x, y]) 59 | if row['fill'] == "rgba(0, 255, 0, 0.8)": 60 | input_labels.append(1) 61 | else: 62 | input_labels.append(0) 63 | 64 | if 'color_change_point' in st.session_state: 65 | p = st.session_state['color_change_point'] 66 | if len(df) < p: 67 | p = len(df) - 1 68 | st.session_state['color_change_point'] = p 69 | masks = model_predict_click(im,input_points[p:],input_labels[p:],model) 70 | else: 71 | masks = model_predict_click(im,input_points,input_labels,model) 72 | 73 | if color_change_point: 74 | st.session_state['color_change_point'] = len(df) 75 | st.session_state['input_masks_color'].append([np.array([]),np.array([])]) 76 | else: 77 | color = np.concatenate([random.choice(get_color()), np.array([0.6])], axis=0) 78 | if 'input_masks_color' not in st.session_state: 79 | st.session_state['input_masks_color'] = [[masks,color]] 80 | 81 | elif not np.array_equal(st.session_state['input_masks_color'][-1][0],masks): 82 | st.session_state['input_masks_color'][-1] = [masks,color] 83 | im_masked = show_click(st.session_state['input_masks_color']) 84 | im_masked = Image.fromarray(im_masked).convert('RGBA') 85 | im = Image.alpha_composite(Image.fromarray(im).convert('RGBA'),im_masked).convert("RGB") 86 | torch.cuda.empty_cache() 87 | rerun = False 88 | if im != st.session_state['im']: 89 | rerun = True 90 | st.session_state['im'] = im 91 | if rerun: 92 | st.experimental_rerun() 93 | im_bytes = BytesIO() 94 | st.session_state['im'].save(im_bytes,format='PNG') 95 | st.download_button('Download image',data=im_bytes.getvalue(),file_name='seg.png') 96 | 97 | def box(container_width,height,scale,radius_width,show_mask,model,im): 98 | for each in ['color_change_point','input_masks_color']: 99 | if each in st.session_state:st.session_state.pop(each) 100 | 101 | canvas_result_1 = st_canvas( 102 | fill_color="rgba(255, 255, 0, 0)", 103 | background_image = st.session_state['im'], 104 | drawing_mode='rect', 105 | stroke_color = "rgba(0, 255, 0, 0.6)", 106 | stroke_width = radius_width, 107 | width = container_width, 108 | height = height * scale, 109 | point_display_radius = 12, 110 | update_streamlit=True, 111 | key="box", 112 | ) 113 | if not show_mask: 114 | im = Image.fromarray(im).convert("RGB") 115 | rerun = False 116 | if im != st.session_state['im']: 117 | rerun = True 118 | st.session_state['im'] = im 119 | if rerun: 120 | st.experimental_rerun() 121 | elif canvas_result_1.json_data is not None: 122 | color_change_point = st.button('Save color') 123 | df = pd.json_normalize(canvas_result_1.json_data["objects"]) 124 | if len(df) == 0: 125 | st.session_state.clear() 126 | if 'canvas_result' not in st.session_state: 127 | st.session_state['canvas_result'] = len(df) 128 | st.experimental_rerun() 129 | elif len(df) != st.session_state['canvas_result']: 130 | st.session_state['canvas_result'] = len(df) 131 | st.experimental_rerun() 132 | return 133 | center_point,center_label,input_box = [],[],[] 134 | for _, row in df.iterrows(): 135 | x, y, w,h = row["left"], row["top"], row["width"], row["height"] 136 | x = int(x/scale) 137 | y = int(y/scale) 138 | w = int(w/scale) 139 | h = int(h/scale) 140 | center_point.append([x+w/2,y+h/2]) 141 | center_label.append([1]) 142 | input_box.append([x,y,x+w,y+h]) 143 | #masks, scores = model_predict_box(im,center_point,center_label,input_box,model) 144 | #im_masked = show_click(masks,scores) 145 | 146 | if 'color_change_point_box' in st.session_state: 147 | p = st.session_state['color_change_point_box'] 148 | if len(df) < p: 149 | p = len(df) - 1 150 | st.session_state['color_change_point_box'] = p 151 | masks = model_predict_box(im,center_point[p:],center_label[p:],input_box[p:],model) 152 | else: 153 | masks = model_predict_box(im,center_point,center_label,input_box,model) 154 | masks = np.array(masks) 155 | if color_change_point: 156 | st.session_state['color_change_point_box'] = len(df) 157 | st.session_state['input_masks_color_box'].append([np.array([]),np.array([])]) 158 | else: 159 | color = np.concatenate([random.choice(get_color()), np.array([0.6])], axis=0) 160 | if 'input_masks_color_box' not in st.session_state: 161 | st.session_state['input_masks_color_box'] = [[masks,color]] 162 | 163 | elif not np.array_equal(st.session_state['input_masks_color_box'][-1][0],masks): 164 | st.session_state['input_masks_color_box'][-1] = [masks,color] 165 | im_masked = show_click(st.session_state['input_masks_color_box']) 166 | im_masked = Image.fromarray(im_masked).convert('RGBA') 167 | im = Image.alpha_composite(Image.fromarray(im).convert('RGBA'),im_masked).convert("RGB") 168 | torch.cuda.empty_cache() 169 | rerun = False 170 | if im != st.session_state['im']: 171 | rerun = True 172 | st.session_state['im'] = im 173 | if rerun: 174 | st.experimental_rerun() 175 | im_bytes = BytesIO() 176 | st.session_state['im'].save(im_bytes,format='PNG') 177 | st.download_button('Download image',data=im_bytes.getvalue(),file_name='seg.png') 178 | 179 | def everthing(im,show_mask,model): 180 | st.session_state.clear() 181 | everything = st.image(Image.fromarray(im)) 182 | if show_mask: 183 | masks = model_predict_everything(im,model) 184 | im_masked = show_everything(masks) 185 | im_masked = Image.fromarray(im_masked).convert('RGBA') 186 | im = Image.alpha_composite(Image.fromarray(im).convert('RGBA'),im_masked).convert("RGB") 187 | everything.image(im) 188 | torch.cuda.empty_cache() 189 | im_bytes = BytesIO() 190 | im.save(im_bytes,format='PNG') 191 | st.download_button('Download image',data=im_bytes.getvalue(),file_name='seg.png') 192 | 193 | def main(): 194 | print('init') 195 | torch.cuda.empty_cache() 196 | with st.sidebar: 197 | im = st.file_uploader(label='Upload image',type=['png','jpg','tif']) 198 | option = st.selectbox( 199 | 'Segmentation mode', 200 | ('Click', 'Box', 'Everything')) 201 | model = st.selectbox( 202 | 'Model', 203 | ('vit_b', 'vit_l', 'vit_h')) 204 | show_mask = st.checkbox('Show mask',value = True) 205 | radius_width = st.slider('Radius/Width for Click/Box',0,20,5,1) 206 | 207 | if im: 208 | im = Image.open(im).convert("RGB") 209 | if 'im' not in st.session_state: 210 | st.session_state['im'] = im 211 | width, height = im.size[:2] 212 | im = np.array(im) 213 | container_width = 700 214 | scale = container_width/width 215 | if option == 'Click': 216 | click(container_width,height,scale,radius_width,show_mask,model,im) 217 | if option == 'Box': 218 | box(container_width,height,scale,radius_width,show_mask,model,im) 219 | if option == 'Everything': 220 | everthing(im,show_mask,model) 221 | else: 222 | st.session_state.clear() 223 | 224 | if __name__ == '__main__': 225 | main() -------------------------------------------------------------------------------- /streamlit_dc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.9.2] - 2022-09-08 9 | 10 | - Fix background image on Streamlit Cloud and remote servers (thanks @andreaferretti) 11 | 12 | ## [0.9.0] - 2022-02-26 13 | 14 | - New `point` mode (thanks @arnauddhaene): 15 | - Adds fixed-radius points to build scatter plots 16 | - Images between frontend and backend are now transferred with URLs computed by Streamlit (thanks @kapong) 17 | - Upgrade `streamlit-component-lib` to 1.3.0 18 | 19 | ## [0.8.0] - 2021-06-06 20 | 21 | - New `polygon` drawing mode (thanks @hiankun): 22 | - left-click will add point 23 | - right click will close polygon 24 | - double click will remove latest point 25 | - the Bin button in the toolbar which deletes the canvas content will now empty the history and send back to Streamlit a blank state, even if `update_streamlit` is set to `False`. 26 | - Right-click fires the `send canvas data back to Streamlit` event for all tools (not only the `polygon`) even if `update_streamlit` is set to `False`. 27 | 28 | ## [0.7.0] - 2021-05-14 29 | 30 | - `initial_drawing` is now used as the initial canvas state. If `None` provided then we create one on the Python side. This provokes the following changes: 31 | - a change in `background_color` will reset the drawing. 32 | - `background_color` will override the background color present in `initial_drawing`. 33 | - if `background_image` is present then `background_color` is removed from `st_canvas` call. 34 | - Upgrade Fabric.js to version 4.4.0. 35 | - Toolbar is now on the bottom left to account for large canvas width. 36 | - Add argument to make the toolbar invisible. 37 | - Make `stroke_width` the minimum size constraint to create a rectangle and circle. Thanks [hiankun](https://github.com/hiankun) for the PR! 38 | 39 | ## [0.6.0] - 2021-01-30 40 | 41 | - Add `initial_drawing` argument to initialize canvas with an exported canvas state 42 | 43 | ## [0.5.2] - 2021-01-23 44 | 45 | - Fix state issue with deleting an object through double click 46 | 47 | ## [0.5.1] - 2020-10-13 48 | 49 | - Add undo/redo/clear buttons 50 | - Add "Send to Streamlit" button for when "Realtime update" is disabled 51 | 52 | ## [0.4.0] - 2020-09-04 53 | 54 | - Add Circle tool 55 | - Add argument to fetch data back to Streamlit on demand 56 | 57 | ## [0.3.0] - 2020-08-27 58 | 59 | ### Added 60 | 61 | - Add Rectangle tool 62 | - Return JSON representation of canvas to Streamlit 63 | - Add background image behind canvas 64 | 65 | ## [0.2.0] - 2020-08-20 66 | 67 | ### Added 68 | 69 | - Add drawing of straight lines 70 | 71 | ### Changed 72 | 73 | - API entrypoint for "drawing_mode" is now of type string 74 | 75 | ## [0.1.1] - 2020-07-14 76 | 77 | - Disable Retina scaling 78 | 79 | ## [0.1.0] - 2020-07-06 80 | 81 | - Drawable canvas widget 82 | -------------------------------------------------------------------------------- /streamlit_dc/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Fanilo Andrianasolo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /streamlit_dc/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include streamlit_drawable_canvas/frontend/build * 2 | -------------------------------------------------------------------------------- /streamlit_dc/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit - Drawable Canvas 2 | 3 | Streamlit component which provides a sketching canvas using [Fabric.js](http://fabricjs.com/). 4 | 5 | [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://share.streamlit.io/andfanilo/streamlit-drawable-canvas-demo/master/app.py) 6 | 7 | [![PyPI](https://img.shields.io/pypi/v/streamlit-drawable-canvas)](https://pypi.org/project/streamlit-drawable-canvas/) 8 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/streamlit-drawable-canvas)](https://pypi.org/project/streamlit-drawable-canvas/) 9 | 10 | Buy Me A Coffee 11 | 12 | ![](./img/demo.gif) 13 | 14 | ## Features 15 | 16 | - Draw freely, lines, circles, boxes and polygons on the canvas, with options on stroke & fill 17 | - Rotate, skew, scale, move any object of the canvas on demand 18 | - Select a background color or image to draw on 19 | - Get image data and every drawn object properties back to Streamlit ! 20 | - Choose to fetch back data in realtime or on demand with a button 21 | - Undo, Redo or Delete canvas contents 22 | - Save canvas data as JSON to reuse for another session 23 | 24 | ## Installation 25 | 26 | ```shell script 27 | pip install streamlit-drawable-canvas 28 | ``` 29 | 30 | ## Example Usage 31 | 32 | Copy this code snippet: 33 | 34 | ```python 35 | import pandas as pd 36 | from PIL import Image 37 | import streamlit as st 38 | from streamlit_drawable_canvas import st_canvas 39 | 40 | # Specify canvas parameters in application 41 | drawing_mode = st.sidebar.selectbox( 42 | "Drawing tool:", ("point", "freedraw", "line", "rect", "circle", "transform") 43 | ) 44 | 45 | stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3) 46 | if drawing_mode == 'point': 47 | point_display_radius = st.sidebar.slider("Point display radius: ", 1, 25, 3) 48 | stroke_color = st.sidebar.color_picker("Stroke color hex: ") 49 | bg_color = st.sidebar.color_picker("Background color hex: ", "#eee") 50 | bg_image = st.sidebar.file_uploader("Background image:", type=["png", "jpg"]) 51 | 52 | realtime_update = st.sidebar.checkbox("Update in realtime", True) 53 | 54 | 55 | 56 | # Create a canvas component 57 | canvas_result = st_canvas( 58 | fill_color="rgba(255, 165, 0, 0.3)", # Fixed fill color with some opacity 59 | stroke_width=stroke_width, 60 | stroke_color=stroke_color, 61 | background_color=bg_color, 62 | background_image=Image.open(bg_image) if bg_image else None, 63 | update_streamlit=realtime_update, 64 | height=150, 65 | drawing_mode=drawing_mode, 66 | point_display_radius=point_display_radius if drawing_mode == 'point' else 0, 67 | key="canvas", 68 | ) 69 | 70 | # Do something interesting with the image data and paths 71 | if canvas_result.image_data is not None: 72 | st.image(canvas_result.image_data) 73 | if canvas_result.json_data is not None: 74 | objects = pd.json_normalize(canvas_result.json_data["objects"]) # need to convert obj to str because PyArrow 75 | for col in objects.select_dtypes(include=['object']).columns: 76 | objects[col] = objects[col].astype("str") 77 | st.dataframe(objects) 78 | ``` 79 | 80 | You will find more detailed examples [on the demo app](https://github.com/andfanilo/streamlit-drawable-canvas-demo/). 81 | 82 | ## API 83 | 84 | ``` 85 | st_canvas( 86 | fill_color: str 87 | stroke_width: int 88 | stroke_color: str 89 | background_color: str 90 | background_image: Image 91 | update_streamlit: bool 92 | height: int 93 | width: int 94 | drawing_mode: str 95 | initial_drawing: dict 96 | display_toolbar: bool 97 | point_display_radius: int 98 | key: str 99 | ) 100 | ``` 101 | 102 | - **fill_color** : Color of fill for Rect in CSS color property. Defaults to "#eee". 103 | - **stroke_width** : Width of drawing brush in CSS color property. Defaults to 20. 104 | - **stroke_color** : Color of drawing brush in hex. Defaults to "black". 105 | - **background_color** : Color of canvas background in CSS color property. Defaults to "" which is transparent. Overriden by background_image. Changing background_color will reset the drawing. 106 | - **background_image** : Pillow Image to display behind canvas. Automatically resized to canvas dimensions. Being behind the canvas, it is not sent back to Streamlit on mouse event. Overrides background_color. Changes to this will reset canvas contents. 107 | - **update_streamlit** : Whenever True, send canvas data to Streamlit when object/selection is updated or mouse up. 108 | - **height** : Height of canvas in pixels. Defaults to 400. 109 | - **width** : Width of canvas in pixels. Defaults to 600. 110 | - **drawing_mode** : Enable free drawing when "freedraw", object manipulation when "transform", otherwise create new objects with "line", "rect", "circle" and "polygon". Defaults to "freedraw". 111 | - On "polygon" mode, double-clicking will remove the latest point and right-clicking will close the polygon. 112 | - **initial_drawing** : Initialize canvas with drawings from here. Should be the `json_data` output from other canvas. Beware: if you try to import a drawing from a bigger/smaller canvas, no rescaling is done in the canvas and the import could fail. 113 | - **point_display_radius** : To make points visible on the canvas, they are drawn as circles. This parameter modifies the radius of the displayed circle. 114 | - **display_toolbar** : If `False`, don't display the undo/redo/delete toolbar. 115 | 116 | Example: 117 | 118 | ```python 119 | import streamlit as st 120 | from streamlit_drawable_canvas import st_canvas 121 | 122 | canvas_result = st_canvas() 123 | st_canvas(initial_drawing=canvas_result.json_data) 124 | ``` 125 | 126 | - **display_toolbar** : Display the undo/redo/reset toolbar. 127 | - **key** : An optional string to use as the unique key for the widget. Assign a key so the component is not remount every time the script is rerun. 128 | 129 | ## Development 130 | 131 | ### Install 132 | 133 | - JS side 134 | 135 | ```shell script 136 | cd frontend 137 | npm install 138 | ``` 139 | 140 | - Python side 141 | 142 | ```shell script 143 | conda create -n streamlit-drawable-canvas python=3.7 144 | conda activate streamlit-drawable-canvas 145 | pip install -e . 146 | ``` 147 | 148 | ### Run 149 | 150 | Both webpack dev server and Streamlit should run at the same time. 151 | 152 | - JS side 153 | 154 | ```shell script 155 | cd frontend 156 | npm run start 157 | ``` 158 | 159 | - Python side 160 | 161 | ```shell script 162 | streamlit run app.py 163 | ``` 164 | 165 | ### Cypress integration tests 166 | 167 | - Install Cypress: `cd e2e; npm i` or `npx install cypress` (with `--force` if cache problem) 168 | - Start Streamlit frontend server: `cd streamlit_drawable_canvas/frontend; npm run start` 169 | - Start Streamlit test script: `streamlit run e2e/app_to_test.py` 170 | - Start Cypress app: `cd e2e; npm run cypress:open` 171 | 172 | ## References 173 | 174 | - [react-sketch](https://github.com/tbolis/react-sketch) 175 | - [React hooks - fabric](https://github.com/fabricjs/fabric.js/issues/5951#issuecomment-563427231) 176 | - [Simulate Retina display](https://stackoverflow.com/questions/12243549/how-to-test-a-webpage-meant-for-retina-display) 177 | - [High DPI Canvas](https://www.html5rocks.com/en/tutorials/canvas/hidpi/) 178 | - [Drawing with FabricJS and TypeScript Part 2: Straight Lines](https://exceptionnotfound.net/drawing-with-fabricjs-and-typescript-part-2-straight-lines/) 179 | - [Drawing with FabricJS and TypeScript Part 7: Undo/Redo](https://exceptionnotfound.net/drawing-with-fabricjs-and-typescript-part-7-undo-redo/) 180 | - [Types for classes as values in TypeScript](https://2ality.com/2020/04/classes-as-values-typescript.html) 181 | - [Working with iframes in Cypress](https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/) 182 | - [How to use useReducer in React Hooks for performance optimization](https://medium.com/crowdbotics/how-to-use-usereducer-in-react-hooks-for-performance-optimization-ecafca9e7bf5) 183 | - [Understanding React Default Props](https://blog.bitsrc.io/understanding-react-default-props-5c50401ed37d) 184 | - [How to avoid passing callbacks down?](https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) 185 | - [Examples of the useReducer Hook](https://daveceddia.com/usereducer-hook-examples/) The `useRef` hook allows you to create a persistent ref to a DOM node, or really to any value. React will persist this value between re-renders (between calls to your component function). 186 | - [CSS filter generator to convert from black to target hex color](https://codepen.io/sosuke/pen/Pjoqqp) 187 | - [When does React re-render components?](https://felixgerschau.com/react-rerender-components/#when-does-react-re-render) 188 | - [Immutable Update Patterns](https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns) 189 | - Icons by [Freepik](https://www.flaticon.com/authors/freepik), [Google](https://www.flaticon.com/authors/google), [Mavadee](https://www.flaticon.com/authors/mavadee). 190 | -------------------------------------------------------------------------------- /streamlit_dc/e2e/app_to_test.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import streamlit as st 3 | from streamlit_drawable_canvas import st_canvas 4 | 5 | st.header("End-to-end Cypress test") 6 | 7 | canvas_result = st_canvas( 8 | fill_color="rgba(255, 165, 0, 0.3)", 9 | stroke_width=10, 10 | stroke_color="green", 11 | background_color="#eee", 12 | height=150, 13 | width=500, 14 | drawing_mode="freedraw", 15 | key="canvas", 16 | ) 17 | 18 | if canvas_result.image_data is not None: 19 | st.image(canvas_result.image_data) 20 | st.dataframe(pd.json_normalize(canvas_result.json_data["objects"])) -------------------------------------------------------------------------------- /streamlit_dc/e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8501", 3 | "chromeWebSecurity": false 4 | } 5 | -------------------------------------------------------------------------------- /streamlit_dc/e2e/cypress/integration/streamlit_canvas_spec.js: -------------------------------------------------------------------------------- 1 | function getIframeBody(index) { 2 | return cy 3 | .get(".element-container > iframe") 4 | .eq(index) 5 | .should(iframe => { 6 | // Wait for a known element of the iframe to exist. In this case, 7 | // we wait for its button to appear. This will happen after the 8 | // handshaking with Streamlit is done. 9 | expect(iframe.contents().find("canvas")).to.exist; 10 | }) 11 | .then(iframe => { 12 | // Return a snapshot of the iframe's body, now that we know it's 13 | // loaded. 14 | return cy.wrap(iframe.contents().find("body")); 15 | }); 16 | } 17 | 18 | describe('Integration Test', () => { 19 | beforeEach(() => { 20 | cy.visit("/"); 21 | 22 | // Make the ribbon decoration line disappear 23 | cy.get(".decoration").invoke("css", "display", "none"); 24 | }); 25 | 26 | it('is rendered correctly', () => { 27 | cy.get(".element-container > .stMarkdown > h2").should("have.text", "End-to-end Cypress test"); 28 | cy.get(".element-container > iframe").should("have.length", 1); 29 | getIframeBody(0).find("canvas").should("have.length", 3); 30 | cy.get(".element-container img").should("have.length", 1); 31 | cy.get(".element-container .stDataFrame").should("have.length", 1); 32 | }) 33 | }) -------------------------------------------------------------------------------- /streamlit_dc/e2e/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /streamlit_dc/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "cypress": "^4.12.0" 4 | }, 5 | "scripts": { 6 | "cypress:open": "cypress open", 7 | "cypress:run": "cypress run" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /streamlit_dc/img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarkMi/segment_anything_streamlit_webui/3991206a625d2e8f04e12d3ff038710e22b3353f/streamlit_dc/img/demo.gif -------------------------------------------------------------------------------- /streamlit_dc/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | from os.path import join 3 | import setuptools 4 | 5 | 6 | def readme() -> str: 7 | """Utility function to read the README file. 8 | Used for the long_description. It's nice, because now 1) we have a top 9 | level README file and 2) it's easier to type in the README file than to put 10 | a raw string in below. 11 | :return: content of README.md 12 | """ 13 | return open(join(dirname(__file__), "README.md")).read() 14 | 15 | 16 | setuptools.setup( 17 | name="streamlit-drawable-canvas", 18 | version="0.10.0", 19 | author="Fanilo ANDRIANASOLO", 20 | author_email="andfanilo@gmail.com", 21 | description="A Streamlit custom component for a free drawing canvas using Fabric.js.", 22 | long_description=readme(), 23 | long_description_content_type="text/markdown", 24 | url="https://github.com/andfanilo/streamlit-drawable-canvas", 25 | packages=setuptools.find_packages(), 26 | include_package_data=True, 27 | classifiers=[], 28 | python_requires=">=3.6", 29 | install_requires=[ 30 | "Pillow", 31 | "numpy", 32 | "streamlit >= 0.63", 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/__init__.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | import os 4 | from dataclasses import dataclass 5 | from hashlib import md5 6 | 7 | import numpy as np 8 | import streamlit.components.v1 as components 9 | import streamlit.elements.image as st_image 10 | from PIL import Image 11 | 12 | _RELEASE = True # on packaging, pass this to True 13 | 14 | if not _RELEASE: 15 | _component_func = components.declare_component( 16 | "st_canvas", 17 | url="http://localhost:3001", 18 | ) 19 | else: 20 | parent_dir = os.path.dirname(os.path.abspath(__file__)) 21 | build_dir = os.path.join(parent_dir, "frontend/build") 22 | _component_func = components.declare_component("st_canvas", path=build_dir) 23 | 24 | 25 | @dataclass 26 | class CanvasResult: 27 | """Dataclass to store output of React Component 28 | 29 | Attributes 30 | ---------- 31 | image_data: np.array 32 | RGBA Matrix of Image Data. 33 | json_data: dict 34 | JSON string of canvas and objects. 35 | """ 36 | 37 | image_data: np.array = None 38 | json_data: dict = None 39 | 40 | 41 | def _data_url_to_image(data_url: str) -> Image: 42 | """Convert DataURL string to the image.""" 43 | _, _data_url = data_url.split(";base64,") 44 | return Image.open(io.BytesIO(base64.b64decode(_data_url))) 45 | 46 | 47 | def _resize_img(img: Image, new_height: int = 700, new_width: int = 700) -> Image: 48 | """Resize the image to the provided resolution.""" 49 | h_ratio = new_height / img.height 50 | w_ratio = new_width / img.width 51 | img = img.resize((int(img.width * w_ratio), int(img.height * h_ratio))) 52 | return img 53 | 54 | 55 | def st_canvas( 56 | fill_color: str = "#eee", 57 | stroke_width: int = 20, 58 | stroke_color: str = "black", 59 | background_color: str = "", 60 | background_image: Image = None, 61 | update_streamlit: bool = True, 62 | height: int = 400, 63 | width: int = 600, 64 | drawing_mode: str = "freedraw", 65 | initial_drawing: dict = None, 66 | display_toolbar: bool = True, 67 | point_display_radius: int = 3, 68 | key=None, 69 | ) -> CanvasResult: 70 | """Create a drawing canvas in Streamlit app. Retrieve the RGBA image data into a 4D numpy array (r, g, b, alpha) 71 | on mouse up event. 72 | 73 | Parameters 74 | ---------- 75 | fill_color: str 76 | Color of fill for Rect in CSS color property. Defaults to "#eee". 77 | stroke_width: str 78 | Width of drawing brush in CSS color property. Defaults to 20. 79 | stroke_color: str 80 | Color of drawing brush in hex. Defaults to "black". 81 | background_color: str 82 | Color of canvas background in CSS color property. Defaults to "" which is transparent. 83 | Overriden by background_image. 84 | Note: Changing background_color will reset the drawing. 85 | background_image: Image 86 | Pillow Image to display behind canvas. 87 | Automatically resized to canvas dimensions. 88 | Being behind the canvas, it is not sent back to Streamlit on mouse event. 89 | update_streamlit: bool 90 | Whenever True, send canvas data to Streamlit when object/selection is updated or mouse up. 91 | height: int 92 | Height of canvas in pixels. Defaults to 400. 93 | width: int 94 | Width of canvas in pixels. Defaults to 600. 95 | drawing_mode: {'freedraw', 'transform', 'line', 'rect', 'circle', 'point', 'polygon'} 96 | Enable free drawing when "freedraw", object manipulation when "transform", "line", "rect", "circle", "point", "polygon". 97 | Defaults to "freedraw". 98 | initial_drawing: dict 99 | Redraw canvas with given initial_drawing. If changed to None then empties canvas. 100 | Should generally be the `json_data` output from other canvas, which you can manipulate. 101 | Beware: if importing from a bigger/smaller canvas, no rescaling is done in the canvas, 102 | it should be ran on user's side. 103 | display_toolbar: bool 104 | Display the undo/redo/reset toolbar. 105 | point_display_radius: int 106 | The radius to use when displaying point objects. Defaults to 3. 107 | key: str 108 | An optional string to use as the unique key for the widget. 109 | Assign a key so the component is not remount every time the script is rerun. 110 | 111 | Returns 112 | ------- 113 | result: CanvasResult 114 | `image_data` contains reshaped RGBA image 4D numpy array (r, g, b, alpha), 115 | `json_data` stores the canvas/objects JSON representation which you can manipulate, store 116 | load and then reinject into another canvas through the `initial_drawing` argument. 117 | """ 118 | # Resize background_image to canvas dimensions by default 119 | # Then override background_color 120 | background_image_url = None 121 | if background_image: 122 | background_image = _resize_img(background_image, height, width) 123 | # Reduce network traffic and cache when switch another configure, use streamlit in-mem filemanager to convert image to URL 124 | background_image_url = st_image.image_to_url( 125 | background_image, width, True, "RGB", "PNG", f"drawable-canvas-bg-{md5(background_image.tobytes()).hexdigest()}-{key}" 126 | ) 127 | # always send relative URLs, the frontend handles this 128 | if background_image_url[0] == '/': 129 | background_image_url = background_image_url[1:] 130 | background_color = "" 131 | 132 | # Clean initial drawing, override its background color 133 | initial_drawing = ( 134 | {"version": "4.4.0"} if initial_drawing is None else initial_drawing 135 | ) 136 | initial_drawing["background"] = background_color 137 | 138 | component_value = _component_func( 139 | fillColor=fill_color, 140 | strokeWidth=stroke_width, 141 | strokeColor=stroke_color, 142 | backgroundColor=background_color, 143 | backgroundImageURL=background_image_url, 144 | realtimeUpdateStreamlit=update_streamlit and (drawing_mode != "polygon"), 145 | canvasHeight=height, 146 | canvasWidth=width, 147 | drawingMode=drawing_mode, 148 | initialDrawing=initial_drawing, 149 | displayToolbar=display_toolbar, 150 | displayRadius=point_display_radius, 151 | key=key, 152 | default=None, 153 | ) 154 | if component_value is None: 155 | return CanvasResult 156 | 157 | return CanvasResult( 158 | np.asarray(_data_url_to_image(component_value["data"])), 159 | component_value["raw"], 160 | ) 161 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/.env: -------------------------------------------------------------------------------- 1 | # Run the component's dev server on :3001 2 | # (The Streamlit dev server already runs on :3000) 3 | PORT=3001 4 | 5 | # Don't automatically open the web browser on `npm run start`. 6 | BROWSER=none 7 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | package-lock.json 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/module.rules: -------------------------------------------------------------------------------- 1 | { 2 | test: /\.m?js/, 3 | type: "javascript/auto", 4 | }, 5 | { 6 | test: /\.m?js/, 7 | resolve: { 8 | fullySpecified: false, 9 | }, 10 | }, 11 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drawable_canvas", 3 | "version": "0.10.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/fabric": "^3.6.2", 10 | "@types/hoist-non-react-statics": "^3.3.1", 11 | "@types/jest": "^24.0.0", 12 | "@types/lodash": "^4.14.161", 13 | "@types/node": "^12.0.0", 14 | "@types/react": "^16.9.0", 15 | "@types/react-dom": "^16.9.0", 16 | "apache-arrow": "^0.17.0", 17 | "event-target-shim": "^5.0.1", 18 | "fabric": "^5.3.0", 19 | "hoist-non-react-statics": "^3.3.2", 20 | "lodash": "^4.17.20", 21 | "react": "^16.13.1", 22 | "react-dom": "^16.13.1", 23 | "react-scripts": "4.0.3", 24 | "streamlit-component-lib": "^1.3.0", 25 | "typescript": "^4.2.3" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": "react-app" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "homepage": "." 49 | } 50 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/package.json.bak: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drawable_canvas", 3 | "version": "0.10.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/fabric": "^3.6.2", 10 | "@types/hoist-non-react-statics": "^3.3.1", 11 | "@types/jest": "^24.0.0", 12 | "@types/lodash": "^4.14.161", 13 | "@types/node": "^12.0.0", 14 | "@types/react": "^16.9.0", 15 | "@types/react-dom": "^16.9.0", 16 | "apache-arrow": ">0.17.0", 17 | "event-target-shim": "^5.0.1", 18 | "fabric": "^5.3.0", 19 | "hoist-non-react-statics": "^3.3.2", 20 | "lodash": "^4.17.20", 21 | "react": "^16.13.1", 22 | "react-dom": "^16.13.1", 23 | "react-scripts": "^5.0.1", 24 | "streamlit-component-lib": "^1.3.0", 25 | "typescript": "~3.7.2" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": "react-app" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "homepage": "." 49 | } 50 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Streamlit Component 9 | 10 | 11 | 12 |
13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/DrawableCanvas.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import { 3 | ComponentProps, 4 | Streamlit, 5 | withStreamlitConnection, 6 | } from "streamlit-component-lib" 7 | import { fabric } from "fabric" 8 | import { isEqual } from "lodash" 9 | 10 | import CanvasToolbar from "./components/CanvasToolbar" 11 | import UpdateStreamlit from "./components/UpdateStreamlit" 12 | 13 | import { useCanvasState } from "./DrawableCanvasState" 14 | import { tools, FabricTool } from "./lib" 15 | 16 | /** 17 | * Arguments Streamlit receives from the Python side 18 | */ 19 | export interface PythonArgs { 20 | fillColor: string 21 | strokeWidth: number 22 | strokeColor: string 23 | backgroundColor: string 24 | backgroundImageURL: string 25 | realtimeUpdateStreamlit: boolean 26 | canvasWidth: number 27 | canvasHeight: number 28 | drawingMode: string 29 | initialDrawing: Object 30 | displayToolbar: boolean 31 | displayRadius: number 32 | } 33 | 34 | /** 35 | * Define logic for the canvas area 36 | */ 37 | const DrawableCanvas = ({ args }: ComponentProps) => { 38 | const { 39 | canvasWidth, 40 | canvasHeight, 41 | backgroundColor, 42 | backgroundImageURL, 43 | realtimeUpdateStreamlit, 44 | drawingMode, 45 | fillColor, 46 | strokeWidth, 47 | strokeColor, 48 | displayRadius, 49 | initialDrawing, 50 | displayToolbar, 51 | }: PythonArgs = args 52 | 53 | /** 54 | * State initialization 55 | */ 56 | const [canvas, setCanvas] = useState(new fabric.Canvas("")) 57 | canvas.stopContextMenu = true 58 | canvas.fireRightClick = true 59 | 60 | const [backgroundCanvas, setBackgroundCanvas] = useState( 61 | new fabric.StaticCanvas("") 62 | ) 63 | const { 64 | canvasState: { 65 | action: { shouldReloadCanvas, forceSendToStreamlit }, 66 | currentState, 67 | initialState, 68 | }, 69 | saveState, 70 | undo, 71 | redo, 72 | canUndo, 73 | canRedo, 74 | forceStreamlitUpdate, 75 | resetState, 76 | } = useCanvasState() 77 | 78 | /** 79 | * Initialize canvases on component mount 80 | * NB: Remount component by changing its key instead of defining deps 81 | */ 82 | useEffect(() => { 83 | const c = new fabric.Canvas("canvas", { 84 | enableRetinaScaling: false, 85 | }) 86 | const imgC = new fabric.StaticCanvas("backgroundimage-canvas", { 87 | enableRetinaScaling: false, 88 | }) 89 | setCanvas(c) 90 | setBackgroundCanvas(imgC) 91 | Streamlit.setFrameHeight() 92 | }, []) 93 | 94 | /** 95 | * Load user drawing into canvas 96 | * Python-side is in charge of initializing drawing with background color if none provided 97 | */ 98 | useEffect(() => { 99 | if (!isEqual(initialState, initialDrawing)) { 100 | canvas.loadFromJSON(initialDrawing, () => { 101 | canvas.renderAll() 102 | resetState(initialDrawing) 103 | }) 104 | } 105 | }, [canvas, initialDrawing, initialState, resetState]) 106 | 107 | /** 108 | * Update background image 109 | */ 110 | useEffect(() => { 111 | if (backgroundImageURL) { 112 | var bgImage = new Image(); 113 | bgImage.onload = function() { 114 | backgroundCanvas.getContext().drawImage(bgImage, 0, 0); 115 | }; 116 | const params = new URLSearchParams(window.location.search); 117 | const baseUrl = params.get('streamlitUrl') 118 | bgImage.src = baseUrl + backgroundImageURL; 119 | } 120 | }, [ 121 | canvas, 122 | backgroundCanvas, 123 | canvasHeight, 124 | canvasWidth, 125 | backgroundColor, 126 | backgroundImageURL, 127 | saveState, 128 | ]) 129 | 130 | /** 131 | * If state changed from undo/redo/reset, update user-facing canvas 132 | */ 133 | useEffect(() => { 134 | if (shouldReloadCanvas) { 135 | canvas.loadFromJSON(currentState, () => {}) 136 | } 137 | }, [canvas, shouldReloadCanvas, currentState]) 138 | 139 | /** 140 | * Update canvas with selected tool 141 | * PS: add initialDrawing in dependency so user drawing update reinits tool 142 | */ 143 | useEffect(() => { 144 | // Update canvas events with selected tool 145 | const selectedTool = new tools[drawingMode](canvas) as FabricTool 146 | const cleanupToolEvents = selectedTool.configureCanvas({ 147 | fillColor: fillColor, 148 | strokeWidth: strokeWidth, 149 | strokeColor: strokeColor, 150 | displayRadius: displayRadius 151 | }) 152 | 153 | canvas.on("mouse:up", (e: any) => { 154 | saveState(canvas.toJSON()) 155 | if (e["button"] === 3) { 156 | forceStreamlitUpdate() 157 | } 158 | }) 159 | 160 | canvas.on("mouse:dblclick", () => { 161 | saveState(canvas.toJSON()) 162 | }) 163 | 164 | // Cleanup tool + send data to Streamlit events 165 | return () => { 166 | cleanupToolEvents() 167 | canvas.off("mouse:up") 168 | canvas.off("mouse:dblclick") 169 | } 170 | }, [ 171 | canvas, 172 | strokeWidth, 173 | strokeColor, 174 | displayRadius, 175 | fillColor, 176 | drawingMode, 177 | initialDrawing, 178 | saveState, 179 | forceStreamlitUpdate, 180 | ]) 181 | 182 | /** 183 | * Render canvas w/ toolbar 184 | */ 185 | return ( 186 |
187 |
196 | 204 |
205 |
213 | 218 |
219 |
227 | 233 |
234 | {displayToolbar && ( 235 | { 244 | resetState(initialState) 245 | }} 246 | /> 247 | )} 248 |
249 | ) 250 | } 251 | 252 | export default withStreamlitConnection(DrawableCanvas) 253 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/DrawableCanvasState.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useReducer, 4 | useContext, 5 | useCallback, 6 | } from "react" 7 | import { isEmpty, isEqual } from "lodash" 8 | 9 | const HISTORY_MAX_COUNT = 100 10 | 11 | interface CanvasHistory { 12 | undoStack: Object[] // store previous canvas states 13 | redoStack: Object[] // store undone canvas states 14 | } 15 | 16 | interface CanvasAction { 17 | shouldReloadCanvas: boolean // reload currentState into app canvas, on undo/redo 18 | forceSendToStreamlit: boolean // send currentState back to Streamlit 19 | } 20 | 21 | const NO_ACTION: CanvasAction = { 22 | shouldReloadCanvas: false, 23 | forceSendToStreamlit: false, 24 | } 25 | 26 | const RELOAD_CANVAS: CanvasAction = { 27 | shouldReloadCanvas: true, 28 | forceSendToStreamlit: false, 29 | } 30 | 31 | const SEND_TO_STREAMLIT: CanvasAction = { 32 | shouldReloadCanvas: false, 33 | forceSendToStreamlit: true, 34 | } 35 | 36 | const RELOAD_AND_SEND_TO_STREAMLIT: CanvasAction = { 37 | shouldReloadCanvas: true, 38 | forceSendToStreamlit: true, 39 | } 40 | 41 | interface CanvasState { 42 | history: CanvasHistory 43 | action: CanvasAction 44 | initialState: Object // first currentState for app 45 | currentState: Object // current canvas state as canvas.toJSON() 46 | } 47 | 48 | interface Action { 49 | type: "save" | "undo" | "redo" | "reset" | "forceSendToStreamlit" 50 | state?: Object 51 | } 52 | 53 | /** 54 | * Reducer takes 5 actions: save, undo, redo, reset, forceSendToStreamlit 55 | * 56 | * On reset, clear everything, set initial and current state to cleared canvas 57 | * 58 | * On save: 59 | * - First, if there is no initial state, set it to current 60 | * Since we don't reset history on component initialization 61 | * As backgroundColor/image are applied after component init 62 | * and wouldn't be stored in initial state 63 | * - If the sent state is same as current state, then nothing has changed so don't save 64 | * - Clear redo stack 65 | * - Push current state to undo stack, delete oldest if necessary 66 | * - Set new current state 67 | * 68 | * On undo: 69 | * - Push state to redoStack if it's not the initial 70 | * - Pop state from undoStack into current state 71 | * 72 | * On redo: 73 | * - Pop state from redoStack into current state 74 | * 75 | * For undo/redo/reset, set shouldReloadCanvas to inject currentState into user facing canvas 76 | */ 77 | const canvasStateReducer = ( 78 | state: CanvasState, 79 | action: Action 80 | ): CanvasState => { 81 | switch (action.type) { 82 | case "save": 83 | if (!action.state) throw new Error("No action state to save") 84 | else if (isEmpty(state.currentState)) { 85 | return { 86 | history: { 87 | undoStack: [], 88 | redoStack: [], 89 | }, 90 | action: { ...NO_ACTION }, 91 | initialState: action.state, 92 | currentState: action.state, 93 | } 94 | } else if (isEqual(action.state, state.currentState)) 95 | return { 96 | history: { ...state.history }, 97 | action: { ...NO_ACTION }, 98 | initialState: state.initialState, 99 | currentState: state.currentState, 100 | } 101 | else { 102 | const undoOverHistoryMaxCount = 103 | state.history.undoStack.length >= HISTORY_MAX_COUNT 104 | return { 105 | history: { 106 | undoStack: [ 107 | ...state.history.undoStack.slice(undoOverHistoryMaxCount ? 1 : 0), 108 | state.currentState, 109 | ], 110 | redoStack: [], 111 | }, 112 | action: { ...NO_ACTION }, 113 | initialState: 114 | state.initialState == null 115 | ? state.currentState 116 | : state.initialState, 117 | currentState: action.state, 118 | } 119 | } 120 | case "undo": 121 | if ( 122 | isEmpty(state.currentState) || 123 | isEqual(state.initialState, state.currentState) 124 | ) { 125 | return { 126 | history: { ...state.history }, 127 | action: { ...NO_ACTION }, 128 | initialState: state.initialState, 129 | currentState: state.currentState, 130 | } 131 | } else { 132 | const isUndoEmpty = state.history.undoStack.length === 0 133 | return { 134 | history: { 135 | undoStack: state.history.undoStack.slice(0, -1), 136 | redoStack: [...state.history.redoStack, state.currentState], 137 | }, 138 | action: { ...RELOAD_CANVAS }, 139 | initialState: state.initialState, 140 | currentState: isUndoEmpty 141 | ? state.currentState 142 | : state.history.undoStack[state.history.undoStack.length - 1], 143 | } 144 | } 145 | case "redo": 146 | if (state.history.redoStack.length > 0) { 147 | // TODO: test currentState empty too ? 148 | return { 149 | history: { 150 | undoStack: [...state.history.undoStack, state.currentState], 151 | redoStack: state.history.redoStack.slice(0, -1), 152 | }, 153 | action: { ...RELOAD_CANVAS }, 154 | initialState: state.initialState, 155 | currentState: 156 | state.history.redoStack[state.history.redoStack.length - 1], 157 | } 158 | } else { 159 | return { 160 | history: { ...state.history }, 161 | action: { ...NO_ACTION }, 162 | initialState: state.initialState, 163 | currentState: state.currentState, 164 | } 165 | } 166 | case "reset": 167 | if (!action.state) throw new Error("No action state to store in reset") 168 | return { 169 | history: { 170 | undoStack: [], 171 | redoStack: [], 172 | }, 173 | action: { ...RELOAD_AND_SEND_TO_STREAMLIT }, 174 | initialState: action.state, 175 | currentState: action.state, 176 | } 177 | case "forceSendToStreamlit": 178 | return { 179 | history: { ...state.history }, 180 | action: { ...SEND_TO_STREAMLIT }, 181 | initialState: state.initialState, 182 | currentState: state.currentState, 183 | } 184 | default: 185 | throw new Error("TS should protect from this") 186 | } 187 | } 188 | 189 | const initialState: CanvasState = { 190 | history: { 191 | undoStack: [], 192 | redoStack: [], 193 | }, 194 | action: { 195 | forceSendToStreamlit: false, 196 | shouldReloadCanvas: false, 197 | }, 198 | initialState: {}, 199 | currentState: {}, 200 | } 201 | 202 | interface CanvasStateContextProps { 203 | canvasState: CanvasState 204 | saveState: (state: Object) => void 205 | undo: () => void 206 | redo: () => void 207 | forceStreamlitUpdate: () => void 208 | canUndo: boolean 209 | canRedo: boolean 210 | resetState: (state: Object) => void 211 | } 212 | 213 | const CanvasStateContext = createContext( 214 | {} as CanvasStateContextProps 215 | ) 216 | 217 | export const CanvasStateProvider = ({ 218 | children, 219 | }: React.PropsWithChildren<{}>) => { 220 | const [canvasState, dispatch] = useReducer(canvasStateReducer, initialState) 221 | 222 | // Setup our callback functions 223 | // We memoize with useCallback to prevent unnecessary re-renders 224 | const saveState = useCallback( 225 | (state) => dispatch({ type: "save", state: state }), 226 | [dispatch] 227 | ) 228 | const undo = useCallback(() => dispatch({ type: "undo" }), [dispatch]) 229 | const redo = useCallback(() => dispatch({ type: "redo" }), [dispatch]) 230 | const forceStreamlitUpdate = useCallback( 231 | () => dispatch({ type: "forceSendToStreamlit" }), 232 | [dispatch] 233 | ) 234 | const resetState = useCallback( 235 | (state) => dispatch({ type: "reset", state: state }), 236 | [dispatch] 237 | ) 238 | 239 | const canUndo = canvasState.history.undoStack.length !== 0 240 | const canRedo = canvasState.history.redoStack.length !== 0 241 | 242 | return ( 243 | 255 | {children} 256 | 257 | ) 258 | } 259 | 260 | /** 261 | * Hook to get data out of context 262 | */ 263 | export const useCanvasState = () => { 264 | return useContext(CanvasStateContext) 265 | } 266 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/components/CanvasToolbar.module.css: -------------------------------------------------------------------------------- 1 | .enabled { 2 | cursor: pointer; 3 | background: none; 4 | } 5 | 6 | .disabled { 7 | cursor: not-allowed; 8 | filter: invert(95%) sepia(10%) saturate(657%) hue-rotate(184deg) 9 | brightness(92%) contrast(95%); 10 | } 11 | 12 | .enabled:hover { 13 | filter: invert(41%) sepia(62%) saturate(7158%) hue-rotate(344deg) 14 | brightness(101%) contrast(108%); 15 | } 16 | 17 | .invertx { 18 | transform: scaleX(-1); 19 | } 20 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/components/CanvasToolbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import styles from "./CanvasToolbar.module.css" 4 | 5 | import bin from "../img/bin.png" 6 | import undo from "../img/undo.png" 7 | import download from "../img/download.png" 8 | 9 | interface SquareIconProps { 10 | imgUrl: string 11 | altText: string 12 | invertX?: boolean 13 | size: number 14 | enabled: boolean 15 | clickCallback: () => void 16 | } 17 | 18 | const SquareIcon = ({ 19 | imgUrl, 20 | altText, 21 | invertX, 22 | size, 23 | enabled, 24 | clickCallback, 25 | }: SquareIconProps) => ( 26 | {altText} 39 | ) 40 | SquareIcon.defaultProps = { 41 | invertX: false, 42 | } 43 | 44 | interface CanvasToolbarProps { 45 | topPosition: number 46 | leftPosition: number 47 | canUndo: boolean 48 | canRedo: boolean 49 | downloadCallback: () => void 50 | undoCallback: () => void 51 | redoCallback: () => void 52 | resetCallback: () => void 53 | } 54 | 55 | const CanvasToolbar = ({ 56 | topPosition, 57 | leftPosition, 58 | canUndo, 59 | canRedo, 60 | downloadCallback, 61 | undoCallback, 62 | redoCallback, 63 | resetCallback, 64 | }: CanvasToolbarProps) => { 65 | const GAP_BETWEEN_ICONS = 4 66 | const ICON_SIZE = 24 67 | 68 | const iconElements = [ 69 | { 70 | imgUrl: download, 71 | altText: "Send to Streamlit", 72 | invertX: false, 73 | enabled: true, 74 | clickCallback: downloadCallback, 75 | }, 76 | { 77 | imgUrl: undo, 78 | altText: "Undo", 79 | invertX: true, 80 | enabled: canUndo, 81 | clickCallback: canUndo ? undoCallback : () => {}, 82 | }, 83 | { 84 | imgUrl: undo, 85 | altText: "Redo", 86 | invertX: false, 87 | enabled: canRedo, 88 | clickCallback: canRedo ? redoCallback : () => {}, 89 | }, 90 | { 91 | imgUrl: bin, 92 | altText: "Reset canvas & history", 93 | invertX: false, 94 | enabled: true, 95 | clickCallback: resetCallback, 96 | }, 97 | ] 98 | 99 | return ( 100 |
110 | {iconElements.map((e) => ( 111 | 120 | ))} 121 |
122 | ) 123 | } 124 | 125 | export default CanvasToolbar 126 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/components/UpdateStreamlit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import { Streamlit } from "streamlit-component-lib" 3 | import { fabric } from "fabric" 4 | 5 | const DELAY_DEBOUNCE = 200 6 | 7 | /** 8 | * Download image and JSON data from canvas to send back to Streamlit 9 | */ 10 | const sendDataToStreamlit = (canvas: fabric.Canvas): void => { 11 | const data = canvas 12 | .getContext() 13 | .canvas.toDataURL() 14 | Streamlit.setComponentValue({ 15 | data: data, 16 | width: canvas.getWidth(), 17 | height: canvas.getHeight(), 18 | raw: canvas.toObject(), 19 | }) 20 | } 21 | 22 | /** 23 | * This hook allows you to debounce any fast changing value. 24 | * The debounced value will only reflect the latest value when the useDebounce hook has not been called for the specified time period. 25 | * When used in conjunction with useEffect, you can easily ensure that expensive operations like API calls are not executed too frequently. 26 | * https://usehooks.com/useDebounce/ 27 | * @param value value to debounce 28 | * @param delay delay of debounce in ms 29 | */ 30 | const useDebounce = (value: any, delay: number) => { 31 | const [debouncedValue, setDebouncedValue] = useState(value) 32 | 33 | useEffect( 34 | () => { 35 | // Update debounced value after delay 36 | const handler = setTimeout(() => { 37 | setDebouncedValue(value) 38 | }, delay) 39 | 40 | // Cancel the timeout if value changes (also on delay change or unmount) 41 | // This is how we prevent debounced value from updating if value is changed ... 42 | // .. within the delay period. Timeout gets cleared and restarted. 43 | return () => { 44 | clearTimeout(handler) 45 | } 46 | }, 47 | [value, delay] // Only re-call effect if value or delay changes 48 | ) 49 | return debouncedValue 50 | } 51 | 52 | interface UpdateStreamlitProps { 53 | shouldSendToStreamlit: boolean 54 | stateToSendToStreamlit: Object 55 | canvasWidth: number 56 | canvasHeight: number 57 | } 58 | 59 | /** 60 | * Canvas whose sole purpose is to draw current state 61 | * to send image data to Streamlit. 62 | * Put it in the background or make it invisible! 63 | */ 64 | const UpdateStreamlit = (props: UpdateStreamlitProps) => { 65 | const [stCanvas, setStCanvas] = useState(new fabric.Canvas("")) 66 | 67 | // Debounce fast changing canvas states 68 | // Especially when drawing lines and circles which continuously render while drawing 69 | const debouncedStateToSend = useDebounce( 70 | props.stateToSendToStreamlit, 71 | DELAY_DEBOUNCE 72 | ) 73 | 74 | // Initialize canvas 75 | useEffect(() => { 76 | const stC = new fabric.Canvas("canvas-to-streamlit", { 77 | enableRetinaScaling: false, 78 | }) 79 | setStCanvas(stC) 80 | }, []) 81 | 82 | // Load state to canvas, then send content to Streamlit 83 | useEffect(() => { 84 | if (debouncedStateToSend && props.shouldSendToStreamlit) { 85 | stCanvas.loadFromJSON(debouncedStateToSend, () => { 86 | sendDataToStreamlit(stCanvas) 87 | }) 88 | } 89 | }, [stCanvas, props.shouldSendToStreamlit, debouncedStateToSend]) 90 | 91 | return ( 92 | 97 | ) 98 | } 99 | 100 | export default UpdateStreamlit 101 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/img/bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarkMi/segment_anything_streamlit_webui/3991206a625d2e8f04e12d3ff038710e22b3353f/streamlit_dc/streamlit_drawable_canvas/frontend/src/img/bin.png -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/img/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarkMi/segment_anything_streamlit_webui/3991206a625d2e8f04e12d3ff038710e22b3353f/streamlit_dc/streamlit_drawable_canvas/frontend/src/img/download.png -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/img/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarkMi/segment_anything_streamlit_webui/3991206a625d2e8f04e12d3ff038710e22b3353f/streamlit_dc/streamlit_drawable_canvas/frontend/src/img/undo.png -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, 6 | ::before, 7 | ::after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | background: transparent; 14 | } 15 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import DrawableCanvas from "./DrawableCanvas" 4 | import { CanvasStateProvider } from "./DrawableCanvasState" 5 | 6 | import "./index.css" 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById("root") 15 | ) 16 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/circle.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 3 | 4 | class CircleTool extends FabricTool { 5 | isMouseDown: boolean = false 6 | fillColor: string = "#ffffff" 7 | strokeWidth: number = 10 8 | strokeColor: string = "#ffffff" 9 | currentCircle: fabric.Circle = new fabric.Circle() 10 | currentStartX: number = 0 11 | currentStartY: number = 0 12 | _minRadius: number = 10 13 | 14 | configureCanvas({ 15 | strokeWidth, 16 | strokeColor, 17 | fillColor, 18 | }: ConfigureCanvasProps): () => void { 19 | this._canvas.isDrawingMode = false 20 | this._canvas.selection = false 21 | this._canvas.forEachObject((o) => (o.selectable = o.evented = false)) 22 | 23 | this.strokeWidth = strokeWidth 24 | this.strokeColor = strokeColor 25 | this.fillColor = fillColor 26 | this._minRadius = strokeWidth 27 | 28 | this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e)) 29 | this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e)) 30 | this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e)) 31 | this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e)) 32 | return () => { 33 | this._canvas.off("mouse:down") 34 | this._canvas.off("mouse:move") 35 | this._canvas.off("mouse:up") 36 | this._canvas.off("mouse:out") 37 | } 38 | } 39 | 40 | onMouseDown(o: any) { 41 | let canvas = this._canvas 42 | let _clicked = o.e["button"] 43 | this.isMouseDown = true 44 | let pointer = canvas.getPointer(o.e) 45 | this.currentStartX = pointer.x 46 | this.currentStartY = pointer.y 47 | this.currentCircle = new fabric.Circle({ 48 | left: this.currentStartX, 49 | top: this.currentStartY, 50 | originX: "left", 51 | originY: "center", 52 | strokeWidth: this.strokeWidth, 53 | stroke: this.strokeColor, 54 | fill: this.fillColor, 55 | selectable: false, 56 | evented: false, 57 | radius: this._minRadius, 58 | }) 59 | if (_clicked === 0) { 60 | canvas.add(this.currentCircle) 61 | } 62 | } 63 | 64 | onMouseMove(o: any) { 65 | if (!this.isMouseDown) return 66 | let canvas = this._canvas 67 | let pointer = canvas.getPointer(o.e) 68 | let _radius = 69 | this.linearDistance( 70 | { x: this.currentStartX, y: this.currentStartY }, 71 | { x: pointer.x, y: pointer.y } 72 | ) / 2 73 | this.currentCircle.set({ 74 | radius: Math.max(_radius, this._minRadius), 75 | angle: 76 | (Math.atan2( 77 | pointer.y - this.currentStartY, 78 | pointer.x - this.currentStartX 79 | ) * 80 | 180) / 81 | Math.PI, 82 | }) 83 | this.currentCircle.setCoords() 84 | canvas.renderAll() 85 | } 86 | 87 | onMouseUp(o: any) { 88 | this.isMouseDown = false 89 | } 90 | 91 | onMouseOut(o: any) { 92 | this.isMouseDown = false 93 | } 94 | 95 | /** 96 | * Calculate the distance of two x,y points 97 | * 98 | * @param point1 an object with x,y attributes representing the start point 99 | * @param point2 an object with x,y attributes representing the end point 100 | * 101 | * @returns {number} 102 | */ 103 | linearDistance = (point1: any, point2: any) => { 104 | let xs = point2.x - point1.x 105 | let ys = point2.y - point1.y 106 | return Math.sqrt(xs * xs + ys * ys) 107 | } 108 | } 109 | 110 | export default CircleTool 111 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/fabrictool.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | 3 | export interface ConfigureCanvasProps { 4 | fillColor: string 5 | strokeWidth: number 6 | strokeColor: string 7 | displayRadius: number 8 | } 9 | 10 | /** 11 | * Base class for any fabric tool that configures and draws on canvas 12 | */ 13 | abstract class FabricTool { 14 | protected _canvas: fabric.Canvas 15 | 16 | /** 17 | * Pass Fabric canvas by reference so tools can configure it 18 | */ 19 | constructor(canvas: fabric.Canvas) { 20 | this._canvas = canvas 21 | } 22 | 23 | /** 24 | * Configure canvas and return a callback to clean eventListeners 25 | * @param args 26 | */ 27 | abstract configureCanvas(args: ConfigureCanvasProps): () => void 28 | } 29 | 30 | export default FabricTool 31 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/freedraw.ts: -------------------------------------------------------------------------------- 1 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 2 | 3 | class FreedrawTool extends FabricTool { 4 | configureCanvas({ 5 | strokeWidth, 6 | strokeColor, 7 | }: ConfigureCanvasProps): () => void { 8 | this._canvas.isDrawingMode = true 9 | this._canvas.freeDrawingBrush.width = strokeWidth 10 | this._canvas.freeDrawingBrush.color = strokeColor 11 | return () => {} 12 | } 13 | } 14 | 15 | export default FreedrawTool 16 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import CircleTool from "./circle" 2 | import FabricTool from "./fabrictool" 3 | import FreedrawTool from "./freedraw" 4 | import LineTool from "./line" 5 | import PolygonTool from "./polygon" 6 | import RectTool from "./rect" 7 | import TransformTool from "./transform" 8 | import PointTool from "./point" 9 | 10 | // TODO: Should make TS happy on the Map of selectedTool --> FabricTool 11 | const tools: any = { 12 | circle: CircleTool, 13 | freedraw: FreedrawTool, 14 | line: LineTool, 15 | polygon: PolygonTool, 16 | rect: RectTool, 17 | transform: TransformTool, 18 | point: PointTool 19 | } 20 | 21 | export { tools, FabricTool } 22 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/line.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 3 | 4 | class LineTool extends FabricTool { 5 | isMouseDown: boolean = false 6 | strokeWidth: number = 10 7 | strokeColor: string = "#ffffff" 8 | currentLine: fabric.Line = new fabric.Line() 9 | 10 | configureCanvas({ 11 | strokeWidth, 12 | strokeColor, 13 | }: ConfigureCanvasProps): () => void { 14 | this._canvas.isDrawingMode = false 15 | this._canvas.selection = false 16 | this._canvas.forEachObject((o) => (o.selectable = o.evented = false)) 17 | 18 | this.strokeWidth = strokeWidth 19 | this.strokeColor = strokeColor 20 | 21 | this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e)) 22 | this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e)) 23 | this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e)) 24 | this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e)) 25 | return () => { 26 | this._canvas.off("mouse:down") 27 | this._canvas.off("mouse:move") 28 | this._canvas.off("mouse:up") 29 | this._canvas.off("mouse:out") 30 | } 31 | } 32 | 33 | onMouseDown(o: any) { 34 | let canvas = this._canvas 35 | let _clicked = o.e["button"] 36 | this.isMouseDown = true 37 | var pointer = canvas.getPointer(o.e) 38 | var points = [pointer.x, pointer.y, pointer.x, pointer.y] 39 | this.currentLine = new fabric.Line(points, { 40 | strokeWidth: this.strokeWidth, 41 | fill: this.strokeColor, 42 | stroke: this.strokeColor, 43 | originX: "center", 44 | originY: "center", 45 | selectable: false, 46 | evented: false, 47 | }) 48 | if (_clicked === 0) { 49 | canvas.add(this.currentLine) 50 | } 51 | } 52 | 53 | onMouseMove(o: any) { 54 | if (!this.isMouseDown) return 55 | let canvas = this._canvas 56 | var pointer = canvas.getPointer(o.e) 57 | this.currentLine.set({ x2: pointer.x, y2: pointer.y }) 58 | this.currentLine.setCoords() 59 | canvas.renderAll() 60 | } 61 | 62 | onMouseUp(o: any) { 63 | this.isMouseDown = false 64 | let canvas = this._canvas 65 | if (this.currentLine.width === 0 && this.currentLine.height === 0) { 66 | canvas.remove(this.currentLine) 67 | } 68 | } 69 | 70 | onMouseOut(o: any) { 71 | this.isMouseDown = false 72 | } 73 | } 74 | 75 | export default LineTool 76 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/point.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 3 | 4 | class PointTool extends FabricTool { 5 | isMouseDown: boolean = false 6 | fillColor: string = "#ffffff" 7 | strokeWidth: number = 10 8 | strokeColor: string = "#ffffff" 9 | currentCircle: fabric.Circle = new fabric.Circle() 10 | currentStartX: number = 0 11 | currentStartY: number = 0 12 | displayRadius: number = 1 13 | 14 | configureCanvas({ 15 | strokeWidth, 16 | strokeColor, 17 | fillColor, 18 | displayRadius 19 | }: ConfigureCanvasProps): () => void { 20 | this._canvas.isDrawingMode = false 21 | this._canvas.selection = false 22 | this._canvas.forEachObject((o) => (o.selectable = o.evented = false)) 23 | 24 | this.strokeWidth = strokeWidth 25 | this.strokeColor = strokeColor 26 | this.fillColor = fillColor 27 | this.displayRadius = displayRadius 28 | 29 | this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e)) 30 | this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e)) 31 | this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e)) 32 | this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e)) 33 | return () => { 34 | this._canvas.off("mouse:down") 35 | this._canvas.off("mouse:move") 36 | this._canvas.off("mouse:up") 37 | this._canvas.off("mouse:out") 38 | } 39 | } 40 | 41 | onMouseDown(o: any) { 42 | let canvas = this._canvas 43 | let _clicked = o.e["button"] 44 | this.isMouseDown = true 45 | let pointer = canvas.getPointer(o.e) 46 | this.currentStartX = pointer.x - (this.displayRadius + this.strokeWidth / 2.) 47 | this.currentStartY = pointer.y //- (this._minRadius + this.strokeWidth) 48 | if (_clicked === 0){ 49 | this.fillColor = "rgba(0, 255, 0, 0.8)" 50 | } 51 | if (_clicked === 2){ 52 | this.fillColor = "rgba(255, 0, 0, 0.8)" 53 | } 54 | this.currentCircle = new fabric.Circle({ 55 | left: this.currentStartX, 56 | top: this.currentStartY, 57 | originX: "left", 58 | originY: "center", 59 | strokeWidth: this.strokeWidth, 60 | stroke: this.strokeColor, 61 | fill: this.fillColor, 62 | selectable: false, 63 | evented: false, 64 | radius: this.displayRadius, 65 | }) 66 | if (_clicked === 0 || _clicked === 2) { 67 | canvas.add(this.currentCircle) 68 | } 69 | } 70 | 71 | onMouseMove(o: any) { 72 | if (!this.isMouseDown) return 73 | let canvas = this._canvas 74 | this.currentCircle.setCoords() 75 | canvas.renderAll() 76 | } 77 | 78 | onMouseUp(o: any) { 79 | this.isMouseDown = false 80 | } 81 | 82 | onMouseOut(o: any) { 83 | this.isMouseDown = false 84 | } 85 | 86 | } 87 | 88 | export default PointTool 89 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/polygon.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 3 | 4 | class PolygonTool extends FabricTool { 5 | isMouseDown: boolean = false 6 | fillColor: string = "#ffffff" 7 | strokeWidth: number = 10 8 | strokeColor: string = "#ffffff" 9 | startCircle: fabric.Circle = new fabric.Circle() 10 | currentLine: fabric.Line = new fabric.Line() 11 | currentPath: fabric.Path = new fabric.Path() 12 | _pathString: string = "M " 13 | 14 | configureCanvas({ 15 | strokeWidth, 16 | strokeColor, 17 | fillColor, 18 | }: ConfigureCanvasProps): () => void { 19 | this._canvas.isDrawingMode = false 20 | this._canvas.selection = false 21 | this._canvas.forEachObject((o) => (o.selectable = o.evented = false)) 22 | 23 | this.strokeWidth = strokeWidth 24 | this.strokeColor = strokeColor 25 | this.fillColor = fillColor 26 | 27 | this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e)) 28 | this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e)) 29 | this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e)) 30 | this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e)) 31 | this._canvas.on("mouse:dblclick", (e: any) => this.onMouseDoubleClick(e)) 32 | return () => { 33 | this._canvas.off("mouse:down") 34 | this._canvas.off("mouse:move") 35 | this._canvas.off("mouse:up") 36 | this._canvas.off("mouse:out") 37 | this._canvas.off("mouse:dblclick") 38 | } 39 | } 40 | 41 | onMouseDown(o: any) { 42 | let canvas = this._canvas 43 | let _clicked = o.e["button"] 44 | let _start = false 45 | if (this._pathString === "M ") { 46 | _start = true 47 | } 48 | 49 | this.isMouseDown = true 50 | var pointer = canvas.getPointer(o.e) 51 | 52 | var points = [pointer.x, pointer.y, pointer.x, pointer.y] 53 | canvas.remove(this.currentLine) 54 | this.currentLine = new fabric.Line(points, { 55 | strokeWidth: this.strokeWidth, 56 | fill: this.strokeColor, 57 | stroke: this.strokeColor, 58 | originX: "center", 59 | originY: "center", 60 | selectable: false, 61 | evented: false, 62 | }) 63 | if (_clicked === 0) { 64 | canvas.add(this.currentLine) 65 | } 66 | 67 | if (_start && _clicked === 0) { 68 | // Initialize pathString 69 | this._pathString += `${pointer.x} ${pointer.y} ` 70 | this.startCircle = new fabric.Circle({ 71 | left: pointer.x, 72 | top: pointer.y, 73 | originX: "center", 74 | originY: "center", 75 | strokeWidth: this.strokeWidth, 76 | stroke: this.strokeColor, 77 | fill: this.strokeColor, 78 | selectable: false, 79 | evented: false, 80 | radius: this.strokeWidth, 81 | }) 82 | canvas.add(this.startCircle) 83 | 84 | _start = false 85 | } else { 86 | canvas.remove(this.currentPath) 87 | if (_clicked === 0) { 88 | // Update pathString 89 | this._pathString += `L ${pointer.x} ${pointer.y} ` 90 | } 91 | if (_clicked === 2) { 92 | // Close pathString 93 | this._pathString += "z" 94 | canvas.remove(this.startCircle) 95 | } 96 | } 97 | this.currentPath = new fabric.Path(this._pathString, { 98 | strokeWidth: this.strokeWidth, 99 | fill: this.fillColor, 100 | stroke: this.strokeColor, 101 | originX: "center", 102 | originY: "center", 103 | selectable: false, 104 | evented: false, 105 | }) 106 | if (this.currentPath.width !== 0 && this.currentPath.height !== 0) { 107 | canvas.add(this.currentPath) 108 | } 109 | if (_clicked === 2) { 110 | this._pathString = "M " 111 | } 112 | } 113 | 114 | onMouseMove(o: any) { 115 | if (!this.isMouseDown) return 116 | let canvas = this._canvas 117 | var pointer = canvas.getPointer(o.e) 118 | this.currentLine.set({ x2: pointer.x, y2: pointer.y }) 119 | this.currentLine.setCoords() 120 | canvas.renderAll() 121 | } 122 | 123 | onMouseUp(o: any) { 124 | this.isMouseDown = true 125 | } 126 | 127 | onMouseOut(o: any) { 128 | this.isMouseDown = false 129 | } 130 | 131 | onMouseDoubleClick(o: any) { 132 | let canvas = this._canvas 133 | // Double click adds two more points at the end, so we have to move back twice more... 134 | for (let i = 0; i < 3; i++) { 135 | let _last_pt_idx = this._pathString.lastIndexOf("L") 136 | if (_last_pt_idx === -1) { 137 | this._pathString = "M " 138 | canvas.remove(this.startCircle) 139 | } else { 140 | this._pathString = this._pathString.slice(0, _last_pt_idx) 141 | } 142 | } 143 | 144 | canvas.remove(this.currentLine) 145 | canvas.remove(this.currentPath) 146 | this.currentPath = new fabric.Path(this._pathString, { 147 | strokeWidth: this.strokeWidth, 148 | fill: this.fillColor, 149 | stroke: this.strokeColor, 150 | originX: "center", 151 | originY: "center", 152 | selectable: false, 153 | evented: false, 154 | }) 155 | canvas.add(this.currentPath) 156 | } 157 | } 158 | export default PolygonTool 159 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/rect.ts: -------------------------------------------------------------------------------- 1 | import { fabric } from "fabric" 2 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 3 | 4 | class RectTool extends FabricTool { 5 | isMouseDown: boolean = false 6 | fillColor: string = "#ffffff" 7 | strokeWidth: number = 10 8 | strokeColor: string = "#ffffff" 9 | currentRect: fabric.Rect = new fabric.Rect() 10 | currentStartX: number = 0 11 | currentStartY: number = 0 12 | _minLength: number = 10 13 | 14 | configureCanvas({ 15 | strokeWidth, 16 | strokeColor, 17 | fillColor, 18 | }: ConfigureCanvasProps): () => void { 19 | this._canvas.isDrawingMode = false 20 | this._canvas.selection = false 21 | this._canvas.forEachObject((o) => (o.selectable = o.evented = false)) 22 | 23 | this.strokeWidth = strokeWidth 24 | this.strokeColor = strokeColor 25 | this.fillColor = fillColor 26 | this._minLength = strokeWidth 27 | 28 | this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e)) 29 | this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e)) 30 | this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e)) 31 | this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e)) 32 | return () => { 33 | this._canvas.off("mouse:down") 34 | this._canvas.off("mouse:move") 35 | this._canvas.off("mouse:up") 36 | this._canvas.off("mouse:out") 37 | } 38 | } 39 | 40 | onMouseDown(o: any) { 41 | let canvas = this._canvas 42 | let _clicked = o.e["button"] 43 | this.isMouseDown = true 44 | let pointer = canvas.getPointer(o.e) 45 | this.currentStartX = pointer.x 46 | this.currentStartY = pointer.y 47 | this.currentRect = new fabric.Rect({ 48 | left: this.currentStartX, 49 | top: this.currentStartY, 50 | originX: "left", 51 | originY: "top", 52 | width: this._minLength, 53 | height: this._minLength, 54 | stroke: this.strokeColor, 55 | strokeWidth: this.strokeWidth, 56 | fill: this.fillColor, 57 | transparentCorners: false, 58 | selectable: false, 59 | evented: false, 60 | strokeUniform: true, 61 | noScaleCache: false, 62 | angle: 0, 63 | }) 64 | if (_clicked === 0) { 65 | canvas.add(this.currentRect) 66 | } 67 | } 68 | 69 | onMouseMove(o: any) { 70 | if (!this.isMouseDown) return 71 | let canvas = this._canvas 72 | let pointer = canvas.getPointer(o.e) 73 | if (this.currentStartX > pointer.x) { 74 | this.currentRect.set({ left: Math.abs(pointer.x) }) 75 | } 76 | if (this.currentStartY > pointer.y) { 77 | this.currentRect.set({ top: Math.abs(pointer.y) }) 78 | } 79 | let _width = Math.abs(this.currentStartX - pointer.x) 80 | let _height = Math.abs(this.currentStartY - pointer.y) 81 | this.currentRect.set({ 82 | width: Math.max(_width, this._minLength * 2), 83 | height: Math.max(_height, this._minLength * 2), 84 | }) 85 | this.currentRect.setCoords() 86 | canvas.renderAll() 87 | } 88 | 89 | onMouseUp(o: any) { 90 | this.isMouseDown = false 91 | } 92 | 93 | onMouseOut(o: any) { 94 | this.isMouseDown = false 95 | } 96 | } 97 | 98 | export default RectTool 99 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/lib/transform.ts: -------------------------------------------------------------------------------- 1 | import FabricTool, { ConfigureCanvasProps } from "./fabrictool" 2 | 3 | class TransformTool extends FabricTool { 4 | configureCanvas(args: ConfigureCanvasProps): () => void { 5 | let canvas = this._canvas 6 | canvas.isDrawingMode = false 7 | canvas.selection = true 8 | canvas.forEachObject((o) => (o.selectable = o.evented = true)) 9 | 10 | // instead of looking for target of double click, 11 | // assume double click on object clears the selected object 12 | const handleDoubleClick = () => { 13 | canvas.remove(canvas.getActiveObject()) 14 | } 15 | 16 | canvas.on("mouse:dblclick", handleDoubleClick) 17 | return () => { 18 | canvas.off("mouse:dblclick", handleDoubleClick) 19 | } 20 | } 21 | } 22 | 23 | export default TransformTool 24 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /streamlit_dc/streamlit_drawable_canvas/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | 2 | from segment_anything import SamPredictor, SamAutomaticMaskGenerator, sam_model_registry 3 | import torch 4 | import numpy as np 5 | from distinctipy import distinctipy 6 | import streamlit as st 7 | 8 | def get_checkpoint_path(model): 9 | if model == 'vit_l': 10 | return 'checkpoint/sam_vit_l_0b3195.pth' 11 | elif model == 'vit_b': 12 | return 'checkpoint/sam_vit_b_01ec64.pth' 13 | elif model == 'vit_h': 14 | return 'checkpoint/sam_vit_h_4b8939.pth' 15 | 16 | 17 | @st.cache_data 18 | def get_color(): 19 | return distinctipy.get_colors(200) 20 | 21 | @st.cache_resource 22 | def get_model(model): 23 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 24 | build_sam = sam_model_registry[model] 25 | model = build_sam(checkpoint=get_checkpoint_path(model)).to(device) 26 | predictor = SamPredictor(model) 27 | mask_generator = SamAutomaticMaskGenerator(model) 28 | torch.cuda.empty_cache() 29 | return predictor, mask_generator 30 | 31 | @st.cache_data 32 | def show_everything(sorted_anns): 33 | if len(sorted_anns) == 0: 34 | return 35 | #sorted_anns = sorted(anns, key=(lambda x: x['stability_score']), reverse=True) 36 | h, w = sorted_anns[0]['segmentation'].shape[-2:] 37 | #sorted_anns = sorted_anns[:int(len(sorted_anns) * stability_score/100)] 38 | if sorted_anns == []: 39 | return np.zeros((h,w,4)).astype(np.uint8) 40 | mask = np.zeros((h,w,4)) 41 | for ann in sorted_anns: 42 | m = ann['segmentation'] 43 | color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0) 44 | mask += m.reshape(h,w,1) * color.reshape(1, 1, -1) 45 | mask = mask * 255 46 | st.success('Process completed!', icon="✅") 47 | return mask.astype(np.uint8) 48 | 49 | @st.cache_data 50 | def show_click(input_masks_color): 51 | h, w = input_masks_color[0][0].shape[-2:] 52 | masks_total = np.zeros((h,w,4)).astype(np.uint8) 53 | for mask, color in input_masks_color: 54 | if np.array_equal(mask,np.array([])):continue 55 | masks = np.zeros((h,w,4)).astype(np.uint8) 56 | masks = masks + mask.reshape(h,w,1).astype(np.uint8) 57 | masks = masks.astype(bool).astype(np.uint8) 58 | masks = masks * 255 * color.reshape(1, 1, -1) 59 | masks_total += masks.astype(np.uint8) 60 | st.success('Process completed!', icon="✅") 61 | return masks_total 62 | 63 | @st.cache_data 64 | def model_predict_everything(im,model): 65 | predictor, mask_generator = get_model(model) 66 | torch.cuda.empty_cache() 67 | return mask_generator.generate(im) 68 | 69 | @st.cache_data 70 | def model_predict_click(im,input_points,input_labels,model): 71 | if input_points == []:return np.array([]) 72 | predictor, mask_generator = get_model(model) 73 | predictor.set_image(im) 74 | input_labels = np.array(input_labels) 75 | input_points = np.array(input_points) 76 | masks, scores, logits = predictor.predict( 77 | point_coords=input_points, 78 | point_labels=input_labels, 79 | multimask_output=False, 80 | ) 81 | torch.cuda.empty_cache() 82 | 83 | return masks 84 | 85 | @st.cache_data 86 | def model_predict_box(im,center_point,center_label,input_box,model): 87 | predictor, mask_generator = get_model(model) 88 | predictor.set_image(im) 89 | masks = np.array([]) 90 | for i in range(len(center_label)): 91 | if center_point[i] == []:continue 92 | center_point_1 = np.array([center_point[i]]) 93 | center_label_1 = np.array(center_label[i]) 94 | input_box_1 = np.array(input_box[i]) 95 | mask, score, logits = predictor.predict( 96 | point_coords=center_point_1, 97 | point_labels=center_label_1, 98 | box=input_box_1, 99 | multimask_output=False, 100 | ) 101 | try: 102 | masks = masks + mask 103 | except: 104 | masks = mask 105 | 106 | torch.cuda.empty_cache() 107 | return masks 108 | --------------------------------------------------------------------------------