├── 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 | 
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 | [](https://share.streamlit.io/andfanilo/streamlit-drawable-canvas-demo/master/app.py)
6 |
7 | [](https://pypi.org/project/streamlit-drawable-canvas/)
8 | [](https://pypi.org/project/streamlit-drawable-canvas/)
9 |
10 |
11 |
12 | 
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 |
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 |
--------------------------------------------------------------------------------