Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitignore +327 -0
- README.md +306 -7
- __init__.py +0 -0
- app.py +17 -0
- css.css +157 -0
- requirements.txt +1 -0
- space.py +146 -0
- src/.gitignore +327 -0
- src/LICENSE +201 -0
- src/README.md +311 -0
- src/backend/gradio_bbox_annotator/__init__.py +4 -0
- src/backend/gradio_bbox_annotator/bbox_annotator.py +187 -0
- src/backend/gradio_bbox_annotator/bbox_annotator.pyi +328 -0
- src/backend/gradio_bbox_annotator/templates/component/index.js +0 -0
- src/backend/gradio_bbox_annotator/templates/component/style.css +0 -0
- src/backend/gradio_bbox_annotator/templates/example/index.js +117 -0
- src/backend/gradio_bbox_annotator/templates/example/style.css +1 -0
- src/demo/__init__.py +0 -0
- src/demo/app.py +17 -0
- src/demo/css.css +157 -0
- src/demo/requirements.txt +1 -0
- src/demo/space.py +146 -0
- src/frontend/Example.svelte +48 -0
- src/frontend/Index.svelte +109 -0
- src/frontend/gradio.config.js +15 -0
- src/frontend/package-lock.json +0 -0
- src/frontend/package.json +58 -0
- src/frontend/shared/AnnotationView.svelte +281 -0
- src/frontend/shared/BoxCursor.svelte +293 -0
- src/frontend/shared/BoxPreview.svelte +42 -0
- src/frontend/shared/ImagePreview.svelte +40 -0
- src/frontend/shared/ImageUploader.svelte +94 -0
- src/frontend/shared/utils.ts +25 -0
- src/frontend/tsconfig.json +3 -0
- src/pyproject.toml +46 -0
.gitignore
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
2 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Python.gitignore
|
3 |
+
|
4 |
+
# Byte-compiled / optimized / DLL files
|
5 |
+
__pycache__/
|
6 |
+
*.py[cod]
|
7 |
+
*$py.class
|
8 |
+
|
9 |
+
# C extensions
|
10 |
+
*.so
|
11 |
+
|
12 |
+
# Distribution / packaging
|
13 |
+
.Python
|
14 |
+
build/
|
15 |
+
develop-eggs/
|
16 |
+
dist/
|
17 |
+
downloads/
|
18 |
+
eggs/
|
19 |
+
.eggs/
|
20 |
+
lib/
|
21 |
+
lib64/
|
22 |
+
parts/
|
23 |
+
sdist/
|
24 |
+
var/
|
25 |
+
wheels/
|
26 |
+
share/python-wheels/
|
27 |
+
*.egg-info/
|
28 |
+
.installed.cfg
|
29 |
+
*.egg
|
30 |
+
MANIFEST
|
31 |
+
|
32 |
+
# PyInstaller
|
33 |
+
# Usually these files are written by a python script from a template
|
34 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
35 |
+
*.manifest
|
36 |
+
*.spec
|
37 |
+
|
38 |
+
# Installer logs
|
39 |
+
pip-log.txt
|
40 |
+
pip-delete-this-directory.txt
|
41 |
+
|
42 |
+
# Unit test / coverage reports
|
43 |
+
htmlcov/
|
44 |
+
.tox/
|
45 |
+
.nox/
|
46 |
+
.coverage
|
47 |
+
.coverage.*
|
48 |
+
.cache
|
49 |
+
nosetests.xml
|
50 |
+
coverage.xml
|
51 |
+
*.cover
|
52 |
+
*.py,cover
|
53 |
+
.hypothesis/
|
54 |
+
.pytest_cache/
|
55 |
+
cover/
|
56 |
+
|
57 |
+
# Translations
|
58 |
+
*.mo
|
59 |
+
*.pot
|
60 |
+
|
61 |
+
# Django stuff:
|
62 |
+
*.log
|
63 |
+
local_settings.py
|
64 |
+
db.sqlite3
|
65 |
+
db.sqlite3-journal
|
66 |
+
|
67 |
+
# Flask stuff:
|
68 |
+
instance/
|
69 |
+
.webassets-cache
|
70 |
+
|
71 |
+
# Scrapy stuff:
|
72 |
+
.scrapy
|
73 |
+
|
74 |
+
# Sphinx documentation
|
75 |
+
docs/_build/
|
76 |
+
|
77 |
+
# PyBuilder
|
78 |
+
.pybuilder/
|
79 |
+
target/
|
80 |
+
|
81 |
+
# Jupyter Notebook
|
82 |
+
.ipynb_checkpoints
|
83 |
+
|
84 |
+
# IPython
|
85 |
+
profile_default/
|
86 |
+
ipython_config.py
|
87 |
+
|
88 |
+
# pyenv
|
89 |
+
# For a library or package, you might want to ignore these files since the code is
|
90 |
+
# intended to run in multiple environments; otherwise, check them in:
|
91 |
+
# .python-version
|
92 |
+
|
93 |
+
# pipenv
|
94 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
95 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
96 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
97 |
+
# install all needed dependencies.
|
98 |
+
#Pipfile.lock
|
99 |
+
|
100 |
+
# poetry
|
101 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
102 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
103 |
+
# commonly ignored for libraries.
|
104 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
105 |
+
#poetry.lock
|
106 |
+
|
107 |
+
# pdm
|
108 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
109 |
+
#pdm.lock
|
110 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
111 |
+
# in version control.
|
112 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
113 |
+
.pdm.toml
|
114 |
+
.pdm-python
|
115 |
+
.pdm-build/
|
116 |
+
|
117 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
118 |
+
__pypackages__/
|
119 |
+
|
120 |
+
# Celery stuff
|
121 |
+
celerybeat-schedule
|
122 |
+
celerybeat.pid
|
123 |
+
|
124 |
+
# SageMath parsed files
|
125 |
+
*.sage.py
|
126 |
+
|
127 |
+
# Environments
|
128 |
+
.env
|
129 |
+
.venv
|
130 |
+
env/
|
131 |
+
venv/
|
132 |
+
ENV/
|
133 |
+
env.bak/
|
134 |
+
venv.bak/
|
135 |
+
|
136 |
+
# Spyder project settings
|
137 |
+
.spyderproject
|
138 |
+
.spyproject
|
139 |
+
|
140 |
+
# Rope project settings
|
141 |
+
.ropeproject
|
142 |
+
|
143 |
+
# mkdocs documentation
|
144 |
+
/site
|
145 |
+
|
146 |
+
# mypy
|
147 |
+
.mypy_cache/
|
148 |
+
.dmypy.json
|
149 |
+
dmypy.json
|
150 |
+
|
151 |
+
# Pyre type checker
|
152 |
+
.pyre/
|
153 |
+
|
154 |
+
# pytype static type analyzer
|
155 |
+
.pytype/
|
156 |
+
|
157 |
+
# Cython debug symbols
|
158 |
+
cython_debug/
|
159 |
+
|
160 |
+
# PyCharm
|
161 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
162 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
163 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
164 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
165 |
+
#.idea/
|
166 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
167 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Node.gitignore
|
168 |
+
|
169 |
+
# Logs
|
170 |
+
logs
|
171 |
+
*.log
|
172 |
+
npm-debug.log*
|
173 |
+
yarn-debug.log*
|
174 |
+
yarn-error.log*
|
175 |
+
lerna-debug.log*
|
176 |
+
.pnpm-debug.log*
|
177 |
+
|
178 |
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
179 |
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
180 |
+
|
181 |
+
# Runtime data
|
182 |
+
pids
|
183 |
+
*.pid
|
184 |
+
*.seed
|
185 |
+
*.pid.lock
|
186 |
+
|
187 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
188 |
+
lib-cov
|
189 |
+
|
190 |
+
# Coverage directory used by tools like istanbul
|
191 |
+
coverage
|
192 |
+
*.lcov
|
193 |
+
|
194 |
+
# nyc test coverage
|
195 |
+
.nyc_output
|
196 |
+
|
197 |
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
198 |
+
.grunt
|
199 |
+
|
200 |
+
# Bower dependency directory (https://bower.io/)
|
201 |
+
bower_components
|
202 |
+
|
203 |
+
# node-waf configuration
|
204 |
+
.lock-wscript
|
205 |
+
|
206 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
207 |
+
build/Release
|
208 |
+
|
209 |
+
# Dependency directories
|
210 |
+
node_modules/
|
211 |
+
jspm_packages/
|
212 |
+
|
213 |
+
# Snowpack dependency directory (https://snowpack.dev/)
|
214 |
+
web_modules/
|
215 |
+
|
216 |
+
# TypeScript cache
|
217 |
+
*.tsbuildinfo
|
218 |
+
|
219 |
+
# Optional npm cache directory
|
220 |
+
.npm
|
221 |
+
|
222 |
+
# Optional eslint cache
|
223 |
+
.eslintcache
|
224 |
+
|
225 |
+
# Optional stylelint cache
|
226 |
+
.stylelintcache
|
227 |
+
|
228 |
+
# Microbundle cache
|
229 |
+
.rpt2_cache/
|
230 |
+
.rts2_cache_cjs/
|
231 |
+
.rts2_cache_es/
|
232 |
+
.rts2_cache_umd/
|
233 |
+
|
234 |
+
# Optional REPL history
|
235 |
+
.node_repl_history
|
236 |
+
|
237 |
+
# Output of 'npm pack'
|
238 |
+
*.tgz
|
239 |
+
|
240 |
+
# Yarn Integrity file
|
241 |
+
.yarn-integrity
|
242 |
+
|
243 |
+
# dotenv environment variable files
|
244 |
+
.env
|
245 |
+
.env.development.local
|
246 |
+
.env.test.local
|
247 |
+
.env.production.local
|
248 |
+
.env.local
|
249 |
+
|
250 |
+
# parcel-bundler cache (https://parceljs.org/)
|
251 |
+
.cache
|
252 |
+
.parcel-cache
|
253 |
+
|
254 |
+
# Next.js build output
|
255 |
+
.next
|
256 |
+
out
|
257 |
+
|
258 |
+
# Nuxt.js build / generate output
|
259 |
+
.nuxt
|
260 |
+
dist
|
261 |
+
|
262 |
+
# Gatsby files
|
263 |
+
.cache/
|
264 |
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
265 |
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
266 |
+
# public
|
267 |
+
|
268 |
+
# vuepress build output
|
269 |
+
.vuepress/dist
|
270 |
+
|
271 |
+
# vuepress v2.x temp and cache directory
|
272 |
+
.temp
|
273 |
+
.cache
|
274 |
+
|
275 |
+
# Docusaurus cache and generated files
|
276 |
+
.docusaurus
|
277 |
+
|
278 |
+
# Serverless directories
|
279 |
+
.serverless/
|
280 |
+
|
281 |
+
# FuseBox cache
|
282 |
+
.fusebox/
|
283 |
+
|
284 |
+
# DynamoDB Local files
|
285 |
+
.dynamodb/
|
286 |
+
|
287 |
+
# TernJS port file
|
288 |
+
.tern-port
|
289 |
+
|
290 |
+
# Stores VSCode versions used for testing VSCode extensions
|
291 |
+
.vscode-test
|
292 |
+
|
293 |
+
# yarn v2
|
294 |
+
.yarn/cache
|
295 |
+
.yarn/unplugged
|
296 |
+
.yarn/build-state.yml
|
297 |
+
.yarn/install-state.gz
|
298 |
+
.pnp.*
|
299 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
300 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Global/macOS.gitignore
|
301 |
+
|
302 |
+
# General
|
303 |
+
.DS_Store
|
304 |
+
.AppleDouble
|
305 |
+
.LSOverride
|
306 |
+
|
307 |
+
# Icon must end with two \r
|
308 |
+
Icon
|
309 |
+
|
310 |
+
# Thumbnails
|
311 |
+
._*
|
312 |
+
|
313 |
+
# Files that might appear in the root of a volume
|
314 |
+
.DocumentRevisions-V100
|
315 |
+
.fseventsd
|
316 |
+
.Spotlight-V100
|
317 |
+
.TemporaryItems
|
318 |
+
.Trashes
|
319 |
+
.VolumeIcon.icns
|
320 |
+
.com.apple.timemachine.donotpresent
|
321 |
+
|
322 |
+
# Directories potentially created on remote AFP share
|
323 |
+
.AppleDB
|
324 |
+
.AppleDesktop
|
325 |
+
Network Trash Folder
|
326 |
+
Temporary Items
|
327 |
+
.apdisk
|
README.md
CHANGED
@@ -1,12 +1,311 @@
|
|
1 |
---
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
|
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 5.8.0
|
8 |
-
app_file: app.py
|
9 |
pinned: false
|
|
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
tags: [gradio-custom-component, SimpleImage, image, annotation, bbox]
|
3 |
+
title: gradio_imageannotator
|
4 |
+
short_description: Bounding box annotation tool
|
5 |
+
colorFrom: blue
|
6 |
+
colorTo: yellow
|
7 |
sdk: gradio
|
|
|
|
|
8 |
pinned: false
|
9 |
+
app_file: space.py
|
10 |
---
|
11 |
|
12 |
+
# `gradio_bbox_annotator`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.1.0%20-%20orange"> <a href="https://github.com/kyamagu/gradio-bbox-annotator/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a>
|
14 |
+
|
15 |
+
Bounding box annotation tool
|
16 |
+
|
17 |
+
## Installation
|
18 |
+
|
19 |
+
```bash
|
20 |
+
pip install gradio_bbox_annotator
|
21 |
+
```
|
22 |
+
|
23 |
+
## Usage
|
24 |
+
|
25 |
+
```python
|
26 |
+
|
27 |
+
import gradio as gr
|
28 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
29 |
+
|
30 |
+
|
31 |
+
example = BBoxAnnotator().example_value()
|
32 |
+
|
33 |
+
demo = gr.Interface(
|
34 |
+
lambda x: x,
|
35 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
36 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
37 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
38 |
+
)
|
39 |
+
|
40 |
+
|
41 |
+
if __name__ == "__main__":
|
42 |
+
demo.launch()
|
43 |
+
|
44 |
+
```
|
45 |
+
|
46 |
+
## `BBoxAnnotator`
|
47 |
+
|
48 |
+
### Initialization
|
49 |
+
|
50 |
+
<table>
|
51 |
+
<thead>
|
52 |
+
<tr>
|
53 |
+
<th align="left">name</th>
|
54 |
+
<th align="left" style="width: 25%;">type</th>
|
55 |
+
<th align="left">default</th>
|
56 |
+
<th align="left">description</th>
|
57 |
+
</tr>
|
58 |
+
</thead>
|
59 |
+
<tbody>
|
60 |
+
<tr>
|
61 |
+
<td align="left"><code>value</code></td>
|
62 |
+
<td align="left" style="width: 25%;">
|
63 |
+
|
64 |
+
```python
|
65 |
+
str
|
66 |
+
| Path
|
67 |
+
| tuple[
|
68 |
+
str | Path,
|
69 |
+
list[tuple[int, int, int, int, str | None]],
|
70 |
+
]
|
71 |
+
| None
|
72 |
+
```
|
73 |
+
|
74 |
+
</td>
|
75 |
+
<td align="left"><code>None</code></td>
|
76 |
+
<td align="left">A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.</td>
|
77 |
+
</tr>
|
78 |
+
|
79 |
+
<tr>
|
80 |
+
<td align="left"><code>categories</code></td>
|
81 |
+
<td align="left" style="width: 25%;">
|
82 |
+
|
83 |
+
```python
|
84 |
+
list[str] | None
|
85 |
+
```
|
86 |
+
|
87 |
+
</td>
|
88 |
+
<td align="left"><code>None</code></td>
|
89 |
+
<td align="left">a list of categories to choose from when annotating the image.</td>
|
90 |
+
</tr>
|
91 |
+
|
92 |
+
<tr>
|
93 |
+
<td align="left"><code>label</code></td>
|
94 |
+
<td align="left" style="width: 25%;">
|
95 |
+
|
96 |
+
```python
|
97 |
+
str | None
|
98 |
+
```
|
99 |
+
|
100 |
+
</td>
|
101 |
+
<td align="left"><code>None</code></td>
|
102 |
+
<td align="left">the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.</td>
|
103 |
+
</tr>
|
104 |
+
|
105 |
+
<tr>
|
106 |
+
<td align="left"><code>every</code></td>
|
107 |
+
<td align="left" style="width: 25%;">
|
108 |
+
|
109 |
+
```python
|
110 |
+
Timer | float | None
|
111 |
+
```
|
112 |
+
|
113 |
+
</td>
|
114 |
+
<td align="left"><code>None</code></td>
|
115 |
+
<td align="left">Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.</td>
|
116 |
+
</tr>
|
117 |
+
|
118 |
+
<tr>
|
119 |
+
<td align="left"><code>inputs</code></td>
|
120 |
+
<td align="left" style="width: 25%;">
|
121 |
+
|
122 |
+
```python
|
123 |
+
Component | Sequence[Component] | set[Component] | None
|
124 |
+
```
|
125 |
+
|
126 |
+
</td>
|
127 |
+
<td align="left"><code>None</code></td>
|
128 |
+
<td align="left">Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.</td>
|
129 |
+
</tr>
|
130 |
+
|
131 |
+
<tr>
|
132 |
+
<td align="left"><code>show_label</code></td>
|
133 |
+
<td align="left" style="width: 25%;">
|
134 |
+
|
135 |
+
```python
|
136 |
+
bool | None
|
137 |
+
```
|
138 |
+
|
139 |
+
</td>
|
140 |
+
<td align="left"><code>None</code></td>
|
141 |
+
<td align="left">if True, will display label.</td>
|
142 |
+
</tr>
|
143 |
+
|
144 |
+
<tr>
|
145 |
+
<td align="left"><code>show_download_button</code></td>
|
146 |
+
<td align="left" style="width: 25%;">
|
147 |
+
|
148 |
+
```python
|
149 |
+
bool
|
150 |
+
```
|
151 |
+
|
152 |
+
</td>
|
153 |
+
<td align="left"><code>True</code></td>
|
154 |
+
<td align="left">If True, will display button to download annotations.</td>
|
155 |
+
</tr>
|
156 |
+
|
157 |
+
<tr>
|
158 |
+
<td align="left"><code>container</code></td>
|
159 |
+
<td align="left" style="width: 25%;">
|
160 |
+
|
161 |
+
```python
|
162 |
+
bool
|
163 |
+
```
|
164 |
+
|
165 |
+
</td>
|
166 |
+
<td align="left"><code>True</code></td>
|
167 |
+
<td align="left">If True, will place the component in a container - providing some extra padding around the border.</td>
|
168 |
+
</tr>
|
169 |
+
|
170 |
+
<tr>
|
171 |
+
<td align="left"><code>scale</code></td>
|
172 |
+
<td align="left" style="width: 25%;">
|
173 |
+
|
174 |
+
```python
|
175 |
+
int | None
|
176 |
+
```
|
177 |
+
|
178 |
+
</td>
|
179 |
+
<td align="left"><code>None</code></td>
|
180 |
+
<td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
|
181 |
+
</tr>
|
182 |
+
|
183 |
+
<tr>
|
184 |
+
<td align="left"><code>min_width</code></td>
|
185 |
+
<td align="left" style="width: 25%;">
|
186 |
+
|
187 |
+
```python
|
188 |
+
int
|
189 |
+
```
|
190 |
+
|
191 |
+
</td>
|
192 |
+
<td align="left"><code>160</code></td>
|
193 |
+
<td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
|
194 |
+
</tr>
|
195 |
+
|
196 |
+
<tr>
|
197 |
+
<td align="left"><code>interactive</code></td>
|
198 |
+
<td align="left" style="width: 25%;">
|
199 |
+
|
200 |
+
```python
|
201 |
+
bool | None
|
202 |
+
```
|
203 |
+
|
204 |
+
</td>
|
205 |
+
<td align="left"><code>None</code></td>
|
206 |
+
<td align="left">if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.</td>
|
207 |
+
</tr>
|
208 |
+
|
209 |
+
<tr>
|
210 |
+
<td align="left"><code>visible</code></td>
|
211 |
+
<td align="left" style="width: 25%;">
|
212 |
+
|
213 |
+
```python
|
214 |
+
bool
|
215 |
+
```
|
216 |
+
|
217 |
+
</td>
|
218 |
+
<td align="left"><code>True</code></td>
|
219 |
+
<td align="left">If False, component will be hidden.</td>
|
220 |
+
</tr>
|
221 |
+
|
222 |
+
<tr>
|
223 |
+
<td align="left"><code>elem_id</code></td>
|
224 |
+
<td align="left" style="width: 25%;">
|
225 |
+
|
226 |
+
```python
|
227 |
+
str | None
|
228 |
+
```
|
229 |
+
|
230 |
+
</td>
|
231 |
+
<td align="left"><code>None</code></td>
|
232 |
+
<td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
|
233 |
+
</tr>
|
234 |
+
|
235 |
+
<tr>
|
236 |
+
<td align="left"><code>elem_classes</code></td>
|
237 |
+
<td align="left" style="width: 25%;">
|
238 |
+
|
239 |
+
```python
|
240 |
+
list[str] | str | None
|
241 |
+
```
|
242 |
+
|
243 |
+
</td>
|
244 |
+
<td align="left"><code>None</code></td>
|
245 |
+
<td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
|
246 |
+
</tr>
|
247 |
+
|
248 |
+
<tr>
|
249 |
+
<td align="left"><code>render</code></td>
|
250 |
+
<td align="left" style="width: 25%;">
|
251 |
+
|
252 |
+
```python
|
253 |
+
bool
|
254 |
+
```
|
255 |
+
|
256 |
+
</td>
|
257 |
+
<td align="left"><code>True</code></td>
|
258 |
+
<td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
|
259 |
+
</tr>
|
260 |
+
|
261 |
+
<tr>
|
262 |
+
<td align="left"><code>key</code></td>
|
263 |
+
<td align="left" style="width: 25%;">
|
264 |
+
|
265 |
+
```python
|
266 |
+
int | str | None
|
267 |
+
```
|
268 |
+
|
269 |
+
</td>
|
270 |
+
<td align="left"><code>None</code></td>
|
271 |
+
<td align="left">if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.</td>
|
272 |
+
</tr>
|
273 |
+
</tbody></table>
|
274 |
+
|
275 |
+
|
276 |
+
### Events
|
277 |
+
|
278 |
+
| name | description |
|
279 |
+
|:-----|:------------|
|
280 |
+
| `clear` | This listener is triggered when the user clears the BBoxAnnotator using the clear button for the component. |
|
281 |
+
| `change` | Triggered when the value of the BBoxAnnotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
|
282 |
+
| `upload` | This listener is triggered when the user uploads a file into the BBoxAnnotator. |
|
283 |
+
|
284 |
+
|
285 |
+
|
286 |
+
### User function
|
287 |
+
|
288 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
289 |
+
|
290 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
291 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
292 |
+
|
293 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
294 |
+
|
295 |
+
- **As output:** Is passed, a tuple of `str` containing the path to the image and a list of annotations.
|
296 |
+
- **As input:** Should return, expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of.
|
297 |
+
|
298 |
+
```python
|
299 |
+
def predict(
|
300 |
+
value: tuple[str, list[tuple[int, int, int, int, str | None]]]
|
301 |
+
| None
|
302 |
+
) -> str
|
303 |
+
| pathlib.Path
|
304 |
+
| tuple[
|
305 |
+
str | pathlib.Path,
|
306 |
+
list[tuple[int, int, int, int, str | None]],
|
307 |
+
]
|
308 |
+
| None:
|
309 |
+
return value
|
310 |
+
```
|
311 |
+
|
__init__.py
ADDED
File without changes
|
app.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
4 |
+
|
5 |
+
|
6 |
+
example = BBoxAnnotator().example_value()
|
7 |
+
|
8 |
+
demo = gr.Interface(
|
9 |
+
lambda x: x,
|
10 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
11 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
12 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
13 |
+
)
|
14 |
+
|
15 |
+
|
16 |
+
if __name__ == "__main__":
|
17 |
+
demo.launch()
|
css.css
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html {
|
2 |
+
font-family: Inter;
|
3 |
+
font-size: 16px;
|
4 |
+
font-weight: 400;
|
5 |
+
line-height: 1.5;
|
6 |
+
-webkit-text-size-adjust: 100%;
|
7 |
+
background: #fff;
|
8 |
+
color: #323232;
|
9 |
+
-webkit-font-smoothing: antialiased;
|
10 |
+
-moz-osx-font-smoothing: grayscale;
|
11 |
+
text-rendering: optimizeLegibility;
|
12 |
+
}
|
13 |
+
|
14 |
+
:root {
|
15 |
+
--space: 1;
|
16 |
+
--vspace: calc(var(--space) * 1rem);
|
17 |
+
--vspace-0: calc(3 * var(--space) * 1rem);
|
18 |
+
--vspace-1: calc(2 * var(--space) * 1rem);
|
19 |
+
--vspace-2: calc(1.5 * var(--space) * 1rem);
|
20 |
+
--vspace-3: calc(0.5 * var(--space) * 1rem);
|
21 |
+
}
|
22 |
+
|
23 |
+
.app {
|
24 |
+
max-width: 748px !important;
|
25 |
+
}
|
26 |
+
|
27 |
+
.prose p {
|
28 |
+
margin: var(--vspace) 0;
|
29 |
+
line-height: var(--vspace * 2);
|
30 |
+
font-size: 1rem;
|
31 |
+
}
|
32 |
+
|
33 |
+
code {
|
34 |
+
font-family: "Inconsolata", sans-serif;
|
35 |
+
font-size: 16px;
|
36 |
+
}
|
37 |
+
|
38 |
+
h1,
|
39 |
+
h1 code {
|
40 |
+
font-weight: 400;
|
41 |
+
line-height: calc(2.5 / var(--space) * var(--vspace));
|
42 |
+
}
|
43 |
+
|
44 |
+
h1 code {
|
45 |
+
background: none;
|
46 |
+
border: none;
|
47 |
+
letter-spacing: 0.05em;
|
48 |
+
padding-bottom: 5px;
|
49 |
+
position: relative;
|
50 |
+
padding: 0;
|
51 |
+
}
|
52 |
+
|
53 |
+
h2 {
|
54 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
55 |
+
line-height: 1em;
|
56 |
+
}
|
57 |
+
|
58 |
+
h3,
|
59 |
+
h3 code {
|
60 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
61 |
+
line-height: 1em;
|
62 |
+
}
|
63 |
+
|
64 |
+
h4,
|
65 |
+
h5,
|
66 |
+
h6 {
|
67 |
+
margin: var(--vspace-3) 0 var(--vspace-3) 0;
|
68 |
+
line-height: var(--vspace);
|
69 |
+
}
|
70 |
+
|
71 |
+
.bigtitle,
|
72 |
+
h1,
|
73 |
+
h1 code {
|
74 |
+
font-size: calc(8px * 4.5);
|
75 |
+
word-break: break-word;
|
76 |
+
}
|
77 |
+
|
78 |
+
.title,
|
79 |
+
h2,
|
80 |
+
h2 code {
|
81 |
+
font-size: calc(8px * 3.375);
|
82 |
+
font-weight: lighter;
|
83 |
+
word-break: break-word;
|
84 |
+
border: none;
|
85 |
+
background: none;
|
86 |
+
}
|
87 |
+
|
88 |
+
.subheading1,
|
89 |
+
h3,
|
90 |
+
h3 code {
|
91 |
+
font-size: calc(8px * 1.8);
|
92 |
+
font-weight: 600;
|
93 |
+
border: none;
|
94 |
+
background: none;
|
95 |
+
letter-spacing: 0.1em;
|
96 |
+
text-transform: uppercase;
|
97 |
+
}
|
98 |
+
|
99 |
+
h2 code {
|
100 |
+
padding: 0;
|
101 |
+
position: relative;
|
102 |
+
letter-spacing: 0.05em;
|
103 |
+
}
|
104 |
+
|
105 |
+
blockquote {
|
106 |
+
font-size: calc(8px * 1.1667);
|
107 |
+
font-style: italic;
|
108 |
+
line-height: calc(1.1667 * var(--vspace));
|
109 |
+
margin: var(--vspace-2) var(--vspace-2);
|
110 |
+
}
|
111 |
+
|
112 |
+
.subheading2,
|
113 |
+
h4 {
|
114 |
+
font-size: calc(8px * 1.4292);
|
115 |
+
text-transform: uppercase;
|
116 |
+
font-weight: 600;
|
117 |
+
}
|
118 |
+
|
119 |
+
.subheading3,
|
120 |
+
h5 {
|
121 |
+
font-size: calc(8px * 1.2917);
|
122 |
+
line-height: calc(1.2917 * var(--vspace));
|
123 |
+
|
124 |
+
font-weight: lighter;
|
125 |
+
text-transform: uppercase;
|
126 |
+
letter-spacing: 0.15em;
|
127 |
+
}
|
128 |
+
|
129 |
+
h6 {
|
130 |
+
font-size: calc(8px * 1.1667);
|
131 |
+
font-size: 1.1667em;
|
132 |
+
font-weight: normal;
|
133 |
+
font-style: italic;
|
134 |
+
font-family: "le-monde-livre-classic-byol", serif !important;
|
135 |
+
letter-spacing: 0px !important;
|
136 |
+
}
|
137 |
+
|
138 |
+
#start .md > *:first-child {
|
139 |
+
margin-top: 0;
|
140 |
+
}
|
141 |
+
|
142 |
+
h2 + h3 {
|
143 |
+
margin-top: 0;
|
144 |
+
}
|
145 |
+
|
146 |
+
.md hr {
|
147 |
+
border: none;
|
148 |
+
border-top: 1px solid var(--block-border-color);
|
149 |
+
margin: var(--vspace-2) 0 var(--vspace-2) 0;
|
150 |
+
}
|
151 |
+
.prose ul {
|
152 |
+
margin: var(--vspace-2) 0 var(--vspace-1) 0;
|
153 |
+
}
|
154 |
+
|
155 |
+
.gap {
|
156 |
+
gap: 0;
|
157 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio_bbox_annotator
|
space.py
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from app import demo as app
|
4 |
+
import os
|
5 |
+
|
6 |
+
_docs = {'BBoxAnnotator': {'description': 'Creates an image component that can be used to upload images with bounding box annotations (as an input)\nor display images with bounding box annotations (as an output). This component can be used to annotate\nimages.', 'members': {'__init__': {'value': {'type': 'str\n | Path\n | tuple[\n str | Path,\n list[tuple[int, int, int, int, str | None]],\n ]\n | None', 'default': 'None', 'description': 'A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.'}, 'categories': {'type': 'list[str] | None', 'default': 'None', 'description': 'a list of categories to choose from when annotating the image.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will display button to download annotations.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | None', 'default': 'None', 'description': 'if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.'}}, 'postprocess': {'value': {'type': 'str\n | pathlib.Path\n | tuple[\n str | pathlib.Path,\n list[tuple[int, int, int, int, str | None]],\n ]\n | None', 'description': 'Expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of'}}, 'preprocess': {'return': {'type': 'tuple[str, list[tuple[int, int, int, int, str | None]]]\n | None', 'description': 'A tuple of `str` containing the path to the image and a list of annotations.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the BBoxAnnotator using the clear button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the BBoxAnnotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the BBoxAnnotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'BBoxAnnotator': []}}}
|
7 |
+
|
8 |
+
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
+
|
10 |
+
with gr.Blocks(
|
11 |
+
css=abs_path,
|
12 |
+
theme=gr.themes.Default(
|
13 |
+
font_mono=[
|
14 |
+
gr.themes.GoogleFont("Inconsolata"),
|
15 |
+
"monospace",
|
16 |
+
],
|
17 |
+
),
|
18 |
+
) as demo:
|
19 |
+
gr.Markdown(
|
20 |
+
"""
|
21 |
+
# `gradio_bbox_annotator`
|
22 |
+
|
23 |
+
<div style="display: flex; gap: 7px;">
|
24 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.1.0%20-%20orange"> <a href="https://github.com/kyamagu/gradio-bbox-annotator/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
Bounding box annotation tool
|
28 |
+
""", elem_classes=["md-custom"], header_links=True)
|
29 |
+
app.render()
|
30 |
+
gr.Markdown(
|
31 |
+
"""
|
32 |
+
## Installation
|
33 |
+
|
34 |
+
```bash
|
35 |
+
pip install gradio_bbox_annotator
|
36 |
+
```
|
37 |
+
|
38 |
+
## Usage
|
39 |
+
|
40 |
+
```python
|
41 |
+
|
42 |
+
import gradio as gr
|
43 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
44 |
+
|
45 |
+
|
46 |
+
example = BBoxAnnotator().example_value()
|
47 |
+
|
48 |
+
demo = gr.Interface(
|
49 |
+
lambda x: x,
|
50 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
51 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
52 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
53 |
+
)
|
54 |
+
|
55 |
+
|
56 |
+
if __name__ == "__main__":
|
57 |
+
demo.launch()
|
58 |
+
|
59 |
+
```
|
60 |
+
""", elem_classes=["md-custom"], header_links=True)
|
61 |
+
|
62 |
+
|
63 |
+
gr.Markdown("""
|
64 |
+
## `BBoxAnnotator`
|
65 |
+
|
66 |
+
### Initialization
|
67 |
+
""", elem_classes=["md-custom"], header_links=True)
|
68 |
+
|
69 |
+
gr.ParamViewer(value=_docs["BBoxAnnotator"]["members"]["__init__"], linkify=[])
|
70 |
+
|
71 |
+
|
72 |
+
gr.Markdown("### Events")
|
73 |
+
gr.ParamViewer(value=_docs["BBoxAnnotator"]["events"], linkify=['Event'])
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
gr.Markdown("""
|
79 |
+
|
80 |
+
### User function
|
81 |
+
|
82 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
83 |
+
|
84 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
85 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
86 |
+
|
87 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
88 |
+
|
89 |
+
- **As input:** Is passed, a tuple of `str` containing the path to the image and a list of annotations.
|
90 |
+
- **As output:** Should return, expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of.
|
91 |
+
|
92 |
+
```python
|
93 |
+
def predict(
|
94 |
+
value: tuple[str, list[tuple[int, int, int, int, str | None]]]
|
95 |
+
| None
|
96 |
+
) -> str
|
97 |
+
| pathlib.Path
|
98 |
+
| tuple[
|
99 |
+
str | pathlib.Path,
|
100 |
+
list[tuple[int, int, int, int, str | None]],
|
101 |
+
]
|
102 |
+
| None:
|
103 |
+
return value
|
104 |
+
```
|
105 |
+
""", elem_classes=["md-custom", "BBoxAnnotator-user-fn"], header_links=True)
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
demo.load(None, js=r"""function() {
|
111 |
+
const refs = {};
|
112 |
+
const user_fn_refs = {
|
113 |
+
BBoxAnnotator: [], };
|
114 |
+
requestAnimationFrame(() => {
|
115 |
+
|
116 |
+
Object.entries(user_fn_refs).forEach(([key, refs]) => {
|
117 |
+
if (refs.length > 0) {
|
118 |
+
const el = document.querySelector(`.${key}-user-fn`);
|
119 |
+
if (!el) return;
|
120 |
+
refs.forEach(ref => {
|
121 |
+
el.innerHTML = el.innerHTML.replace(
|
122 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
123 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
124 |
+
);
|
125 |
+
})
|
126 |
+
}
|
127 |
+
})
|
128 |
+
|
129 |
+
Object.entries(refs).forEach(([key, refs]) => {
|
130 |
+
if (refs.length > 0) {
|
131 |
+
const el = document.querySelector(`.${key}`);
|
132 |
+
if (!el) return;
|
133 |
+
refs.forEach(ref => {
|
134 |
+
el.innerHTML = el.innerHTML.replace(
|
135 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
136 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
137 |
+
);
|
138 |
+
})
|
139 |
+
}
|
140 |
+
})
|
141 |
+
})
|
142 |
+
}
|
143 |
+
|
144 |
+
""")
|
145 |
+
|
146 |
+
demo.launch()
|
src/.gitignore
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
2 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Python.gitignore
|
3 |
+
|
4 |
+
# Byte-compiled / optimized / DLL files
|
5 |
+
__pycache__/
|
6 |
+
*.py[cod]
|
7 |
+
*$py.class
|
8 |
+
|
9 |
+
# C extensions
|
10 |
+
*.so
|
11 |
+
|
12 |
+
# Distribution / packaging
|
13 |
+
.Python
|
14 |
+
build/
|
15 |
+
develop-eggs/
|
16 |
+
dist/
|
17 |
+
downloads/
|
18 |
+
eggs/
|
19 |
+
.eggs/
|
20 |
+
lib/
|
21 |
+
lib64/
|
22 |
+
parts/
|
23 |
+
sdist/
|
24 |
+
var/
|
25 |
+
wheels/
|
26 |
+
share/python-wheels/
|
27 |
+
*.egg-info/
|
28 |
+
.installed.cfg
|
29 |
+
*.egg
|
30 |
+
MANIFEST
|
31 |
+
|
32 |
+
# PyInstaller
|
33 |
+
# Usually these files are written by a python script from a template
|
34 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
35 |
+
*.manifest
|
36 |
+
*.spec
|
37 |
+
|
38 |
+
# Installer logs
|
39 |
+
pip-log.txt
|
40 |
+
pip-delete-this-directory.txt
|
41 |
+
|
42 |
+
# Unit test / coverage reports
|
43 |
+
htmlcov/
|
44 |
+
.tox/
|
45 |
+
.nox/
|
46 |
+
.coverage
|
47 |
+
.coverage.*
|
48 |
+
.cache
|
49 |
+
nosetests.xml
|
50 |
+
coverage.xml
|
51 |
+
*.cover
|
52 |
+
*.py,cover
|
53 |
+
.hypothesis/
|
54 |
+
.pytest_cache/
|
55 |
+
cover/
|
56 |
+
|
57 |
+
# Translations
|
58 |
+
*.mo
|
59 |
+
*.pot
|
60 |
+
|
61 |
+
# Django stuff:
|
62 |
+
*.log
|
63 |
+
local_settings.py
|
64 |
+
db.sqlite3
|
65 |
+
db.sqlite3-journal
|
66 |
+
|
67 |
+
# Flask stuff:
|
68 |
+
instance/
|
69 |
+
.webassets-cache
|
70 |
+
|
71 |
+
# Scrapy stuff:
|
72 |
+
.scrapy
|
73 |
+
|
74 |
+
# Sphinx documentation
|
75 |
+
docs/_build/
|
76 |
+
|
77 |
+
# PyBuilder
|
78 |
+
.pybuilder/
|
79 |
+
target/
|
80 |
+
|
81 |
+
# Jupyter Notebook
|
82 |
+
.ipynb_checkpoints
|
83 |
+
|
84 |
+
# IPython
|
85 |
+
profile_default/
|
86 |
+
ipython_config.py
|
87 |
+
|
88 |
+
# pyenv
|
89 |
+
# For a library or package, you might want to ignore these files since the code is
|
90 |
+
# intended to run in multiple environments; otherwise, check them in:
|
91 |
+
# .python-version
|
92 |
+
|
93 |
+
# pipenv
|
94 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
95 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
96 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
97 |
+
# install all needed dependencies.
|
98 |
+
#Pipfile.lock
|
99 |
+
|
100 |
+
# poetry
|
101 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
102 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
103 |
+
# commonly ignored for libraries.
|
104 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
105 |
+
#poetry.lock
|
106 |
+
|
107 |
+
# pdm
|
108 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
109 |
+
#pdm.lock
|
110 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
111 |
+
# in version control.
|
112 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
113 |
+
.pdm.toml
|
114 |
+
.pdm-python
|
115 |
+
.pdm-build/
|
116 |
+
|
117 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
118 |
+
__pypackages__/
|
119 |
+
|
120 |
+
# Celery stuff
|
121 |
+
celerybeat-schedule
|
122 |
+
celerybeat.pid
|
123 |
+
|
124 |
+
# SageMath parsed files
|
125 |
+
*.sage.py
|
126 |
+
|
127 |
+
# Environments
|
128 |
+
.env
|
129 |
+
.venv
|
130 |
+
env/
|
131 |
+
venv/
|
132 |
+
ENV/
|
133 |
+
env.bak/
|
134 |
+
venv.bak/
|
135 |
+
|
136 |
+
# Spyder project settings
|
137 |
+
.spyderproject
|
138 |
+
.spyproject
|
139 |
+
|
140 |
+
# Rope project settings
|
141 |
+
.ropeproject
|
142 |
+
|
143 |
+
# mkdocs documentation
|
144 |
+
/site
|
145 |
+
|
146 |
+
# mypy
|
147 |
+
.mypy_cache/
|
148 |
+
.dmypy.json
|
149 |
+
dmypy.json
|
150 |
+
|
151 |
+
# Pyre type checker
|
152 |
+
.pyre/
|
153 |
+
|
154 |
+
# pytype static type analyzer
|
155 |
+
.pytype/
|
156 |
+
|
157 |
+
# Cython debug symbols
|
158 |
+
cython_debug/
|
159 |
+
|
160 |
+
# PyCharm
|
161 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
162 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
163 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
164 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
165 |
+
#.idea/
|
166 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
167 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Node.gitignore
|
168 |
+
|
169 |
+
# Logs
|
170 |
+
logs
|
171 |
+
*.log
|
172 |
+
npm-debug.log*
|
173 |
+
yarn-debug.log*
|
174 |
+
yarn-error.log*
|
175 |
+
lerna-debug.log*
|
176 |
+
.pnpm-debug.log*
|
177 |
+
|
178 |
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
179 |
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
180 |
+
|
181 |
+
# Runtime data
|
182 |
+
pids
|
183 |
+
*.pid
|
184 |
+
*.seed
|
185 |
+
*.pid.lock
|
186 |
+
|
187 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
188 |
+
lib-cov
|
189 |
+
|
190 |
+
# Coverage directory used by tools like istanbul
|
191 |
+
coverage
|
192 |
+
*.lcov
|
193 |
+
|
194 |
+
# nyc test coverage
|
195 |
+
.nyc_output
|
196 |
+
|
197 |
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
198 |
+
.grunt
|
199 |
+
|
200 |
+
# Bower dependency directory (https://bower.io/)
|
201 |
+
bower_components
|
202 |
+
|
203 |
+
# node-waf configuration
|
204 |
+
.lock-wscript
|
205 |
+
|
206 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
207 |
+
build/Release
|
208 |
+
|
209 |
+
# Dependency directories
|
210 |
+
node_modules/
|
211 |
+
jspm_packages/
|
212 |
+
|
213 |
+
# Snowpack dependency directory (https://snowpack.dev/)
|
214 |
+
web_modules/
|
215 |
+
|
216 |
+
# TypeScript cache
|
217 |
+
*.tsbuildinfo
|
218 |
+
|
219 |
+
# Optional npm cache directory
|
220 |
+
.npm
|
221 |
+
|
222 |
+
# Optional eslint cache
|
223 |
+
.eslintcache
|
224 |
+
|
225 |
+
# Optional stylelint cache
|
226 |
+
.stylelintcache
|
227 |
+
|
228 |
+
# Microbundle cache
|
229 |
+
.rpt2_cache/
|
230 |
+
.rts2_cache_cjs/
|
231 |
+
.rts2_cache_es/
|
232 |
+
.rts2_cache_umd/
|
233 |
+
|
234 |
+
# Optional REPL history
|
235 |
+
.node_repl_history
|
236 |
+
|
237 |
+
# Output of 'npm pack'
|
238 |
+
*.tgz
|
239 |
+
|
240 |
+
# Yarn Integrity file
|
241 |
+
.yarn-integrity
|
242 |
+
|
243 |
+
# dotenv environment variable files
|
244 |
+
.env
|
245 |
+
.env.development.local
|
246 |
+
.env.test.local
|
247 |
+
.env.production.local
|
248 |
+
.env.local
|
249 |
+
|
250 |
+
# parcel-bundler cache (https://parceljs.org/)
|
251 |
+
.cache
|
252 |
+
.parcel-cache
|
253 |
+
|
254 |
+
# Next.js build output
|
255 |
+
.next
|
256 |
+
out
|
257 |
+
|
258 |
+
# Nuxt.js build / generate output
|
259 |
+
.nuxt
|
260 |
+
dist
|
261 |
+
|
262 |
+
# Gatsby files
|
263 |
+
.cache/
|
264 |
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
265 |
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
266 |
+
# public
|
267 |
+
|
268 |
+
# vuepress build output
|
269 |
+
.vuepress/dist
|
270 |
+
|
271 |
+
# vuepress v2.x temp and cache directory
|
272 |
+
.temp
|
273 |
+
.cache
|
274 |
+
|
275 |
+
# Docusaurus cache and generated files
|
276 |
+
.docusaurus
|
277 |
+
|
278 |
+
# Serverless directories
|
279 |
+
.serverless/
|
280 |
+
|
281 |
+
# FuseBox cache
|
282 |
+
.fusebox/
|
283 |
+
|
284 |
+
# DynamoDB Local files
|
285 |
+
.dynamodb/
|
286 |
+
|
287 |
+
# TernJS port file
|
288 |
+
.tern-port
|
289 |
+
|
290 |
+
# Stores VSCode versions used for testing VSCode extensions
|
291 |
+
.vscode-test
|
292 |
+
|
293 |
+
# yarn v2
|
294 |
+
.yarn/cache
|
295 |
+
.yarn/unplugged
|
296 |
+
.yarn/build-state.yml
|
297 |
+
.yarn/install-state.gz
|
298 |
+
.pnp.*
|
299 |
+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
|
300 |
+
### https://raw.github.com/github/gitignore/5445a270254a0b4443b20ed0033c4094959f937e/Global/macOS.gitignore
|
301 |
+
|
302 |
+
# General
|
303 |
+
.DS_Store
|
304 |
+
.AppleDouble
|
305 |
+
.LSOverride
|
306 |
+
|
307 |
+
# Icon must end with two \r
|
308 |
+
Icon
|
309 |
+
|
310 |
+
# Thumbnails
|
311 |
+
._*
|
312 |
+
|
313 |
+
# Files that might appear in the root of a volume
|
314 |
+
.DocumentRevisions-V100
|
315 |
+
.fseventsd
|
316 |
+
.Spotlight-V100
|
317 |
+
.TemporaryItems
|
318 |
+
.Trashes
|
319 |
+
.VolumeIcon.icns
|
320 |
+
.com.apple.timemachine.donotpresent
|
321 |
+
|
322 |
+
# Directories potentially created on remote AFP share
|
323 |
+
.AppleDB
|
324 |
+
.AppleDesktop
|
325 |
+
Network Trash Folder
|
326 |
+
Temporary Items
|
327 |
+
.apdisk
|
src/LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Apache License
|
2 |
+
Version 2.0, January 2004
|
3 |
+
http://www.apache.org/licenses/
|
4 |
+
|
5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6 |
+
|
7 |
+
1. Definitions.
|
8 |
+
|
9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
11 |
+
|
12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13 |
+
the copyright owner that is granting the License.
|
14 |
+
|
15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
16 |
+
other entities that control, are controlled by, or are under common
|
17 |
+
control with that entity. For the purposes of this definition,
|
18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
19 |
+
direction or management of such entity, whether by contract or
|
20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22 |
+
|
23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24 |
+
exercising permissions granted by this License.
|
25 |
+
|
26 |
+
"Source" form shall mean the preferred form for making modifications,
|
27 |
+
including but not limited to software source code, documentation
|
28 |
+
source, and configuration files.
|
29 |
+
|
30 |
+
"Object" form shall mean any form resulting from mechanical
|
31 |
+
transformation or translation of a Source form, including but
|
32 |
+
not limited to compiled object code, generated documentation,
|
33 |
+
and conversions to other media types.
|
34 |
+
|
35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
36 |
+
Object form, made available under the License, as indicated by a
|
37 |
+
copyright notice that is included in or attached to the work
|
38 |
+
(an example is provided in the Appendix below).
|
39 |
+
|
40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41 |
+
form, that is based on (or derived from) the Work and for which the
|
42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
44 |
+
of this License, Derivative Works shall not include works that remain
|
45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46 |
+
the Work and Derivative Works thereof.
|
47 |
+
|
48 |
+
"Contribution" shall mean any work of authorship, including
|
49 |
+
the original version of the Work and any modifications or additions
|
50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
54 |
+
means any form of electronic, verbal, or written communication sent
|
55 |
+
to the Licensor or its representatives, including but not limited to
|
56 |
+
communication on electronic mailing lists, source code control systems,
|
57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
59 |
+
excluding communication that is conspicuously marked or otherwise
|
60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
61 |
+
|
62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
64 |
+
subsequently incorporated within the Work.
|
65 |
+
|
66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
71 |
+
Work and such Derivative Works in Source or Object form.
|
72 |
+
|
73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76 |
+
(except as stated in this section) patent license to make, have made,
|
77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78 |
+
where such license applies only to those patent claims licensable
|
79 |
+
by such Contributor that are necessarily infringed by their
|
80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
82 |
+
institute patent litigation against any entity (including a
|
83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84 |
+
or a Contribution incorporated within the Work constitutes direct
|
85 |
+
or contributory patent infringement, then any patent licenses
|
86 |
+
granted to You under this License for that Work shall terminate
|
87 |
+
as of the date such litigation is filed.
|
88 |
+
|
89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
90 |
+
Work or Derivative Works thereof in any medium, with or without
|
91 |
+
modifications, and in Source or Object form, provided that You
|
92 |
+
meet the following conditions:
|
93 |
+
|
94 |
+
(a) You must give any other recipients of the Work or
|
95 |
+
Derivative Works a copy of this License; and
|
96 |
+
|
97 |
+
(b) You must cause any modified files to carry prominent notices
|
98 |
+
stating that You changed the files; and
|
99 |
+
|
100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
101 |
+
that You distribute, all copyright, patent, trademark, and
|
102 |
+
attribution notices from the Source form of the Work,
|
103 |
+
excluding those notices that do not pertain to any part of
|
104 |
+
the Derivative Works; and
|
105 |
+
|
106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107 |
+
distribution, then any Derivative Works that You distribute must
|
108 |
+
include a readable copy of the attribution notices contained
|
109 |
+
within such NOTICE file, excluding those notices that do not
|
110 |
+
pertain to any part of the Derivative Works, in at least one
|
111 |
+
of the following places: within a NOTICE text file distributed
|
112 |
+
as part of the Derivative Works; within the Source form or
|
113 |
+
documentation, if provided along with the Derivative Works; or,
|
114 |
+
within a display generated by the Derivative Works, if and
|
115 |
+
wherever such third-party notices normally appear. The contents
|
116 |
+
of the NOTICE file are for informational purposes only and
|
117 |
+
do not modify the License. You may add Your own attribution
|
118 |
+
notices within Derivative Works that You distribute, alongside
|
119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
120 |
+
that such additional attribution notices cannot be construed
|
121 |
+
as modifying the License.
|
122 |
+
|
123 |
+
You may add Your own copyright statement to Your modifications and
|
124 |
+
may provide additional or different license terms and conditions
|
125 |
+
for use, reproduction, or distribution of Your modifications, or
|
126 |
+
for any such Derivative Works as a whole, provided Your use,
|
127 |
+
reproduction, and distribution of the Work otherwise complies with
|
128 |
+
the conditions stated in this License.
|
129 |
+
|
130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
132 |
+
by You to the Licensor shall be under the terms and conditions of
|
133 |
+
this License, without any additional terms or conditions.
|
134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135 |
+
the terms of any separate license agreement you may have executed
|
136 |
+
with Licensor regarding such Contributions.
|
137 |
+
|
138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
140 |
+
except as required for reasonable and customary use in describing the
|
141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
142 |
+
|
143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144 |
+
agreed to in writing, Licensor provides the Work (and each
|
145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147 |
+
implied, including, without limitation, any warranties or conditions
|
148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150 |
+
appropriateness of using or redistributing the Work and assume any
|
151 |
+
risks associated with Your exercise of permissions under this License.
|
152 |
+
|
153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
154 |
+
whether in tort (including negligence), contract, or otherwise,
|
155 |
+
unless required by applicable law (such as deliberate and grossly
|
156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157 |
+
liable to You for damages, including any direct, indirect, special,
|
158 |
+
incidental, or consequential damages of any character arising as a
|
159 |
+
result of this License or out of the use or inability to use the
|
160 |
+
Work (including but not limited to damages for loss of goodwill,
|
161 |
+
work stoppage, computer failure or malfunction, or any and all
|
162 |
+
other commercial damages or losses), even if such Contributor
|
163 |
+
has been advised of the possibility of such damages.
|
164 |
+
|
165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168 |
+
or other liability obligations and/or rights consistent with this
|
169 |
+
License. However, in accepting such obligations, You may act only
|
170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171 |
+
of any other Contributor, and only if You agree to indemnify,
|
172 |
+
defend, and hold each Contributor harmless for any liability
|
173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
174 |
+
of your accepting any such warranty or additional liability.
|
175 |
+
|
176 |
+
END OF TERMS AND CONDITIONS
|
177 |
+
|
178 |
+
APPENDIX: How to apply the Apache License to your work.
|
179 |
+
|
180 |
+
To apply the Apache License to your work, attach the following
|
181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182 |
+
replaced with your own identifying information. (Don't include
|
183 |
+
the brackets!) The text should be enclosed in the appropriate
|
184 |
+
comment syntax for the file format. We also recommend that a
|
185 |
+
file or class name and description of purpose be included on the
|
186 |
+
same "printed page" as the copyright notice for easier
|
187 |
+
identification within third-party archives.
|
188 |
+
|
189 |
+
Copyright [yyyy] [name of copyright owner]
|
190 |
+
|
191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192 |
+
you may not use this file except in compliance with the License.
|
193 |
+
You may obtain a copy of the License at
|
194 |
+
|
195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
196 |
+
|
197 |
+
Unless required by applicable law or agreed to in writing, software
|
198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200 |
+
See the License for the specific language governing permissions and
|
201 |
+
limitations under the License.
|
src/README.md
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
tags: [gradio-custom-component, SimpleImage, image, annotation, bbox]
|
3 |
+
title: gradio_imageannotator
|
4 |
+
short_description: Bounding box annotation tool
|
5 |
+
colorFrom: blue
|
6 |
+
colorTo: yellow
|
7 |
+
sdk: gradio
|
8 |
+
pinned: false
|
9 |
+
app_file: space.py
|
10 |
+
---
|
11 |
+
|
12 |
+
# `gradio_bbox_annotator`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.1.0%20-%20orange"> <a href="https://github.com/kyamagu/gradio-bbox-annotator/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a>
|
14 |
+
|
15 |
+
Bounding box annotation tool
|
16 |
+
|
17 |
+
## Installation
|
18 |
+
|
19 |
+
```bash
|
20 |
+
pip install gradio_bbox_annotator
|
21 |
+
```
|
22 |
+
|
23 |
+
## Usage
|
24 |
+
|
25 |
+
```python
|
26 |
+
|
27 |
+
import gradio as gr
|
28 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
29 |
+
|
30 |
+
|
31 |
+
example = BBoxAnnotator().example_value()
|
32 |
+
|
33 |
+
demo = gr.Interface(
|
34 |
+
lambda x: x,
|
35 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
36 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
37 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
38 |
+
)
|
39 |
+
|
40 |
+
|
41 |
+
if __name__ == "__main__":
|
42 |
+
demo.launch()
|
43 |
+
|
44 |
+
```
|
45 |
+
|
46 |
+
## `BBoxAnnotator`
|
47 |
+
|
48 |
+
### Initialization
|
49 |
+
|
50 |
+
<table>
|
51 |
+
<thead>
|
52 |
+
<tr>
|
53 |
+
<th align="left">name</th>
|
54 |
+
<th align="left" style="width: 25%;">type</th>
|
55 |
+
<th align="left">default</th>
|
56 |
+
<th align="left">description</th>
|
57 |
+
</tr>
|
58 |
+
</thead>
|
59 |
+
<tbody>
|
60 |
+
<tr>
|
61 |
+
<td align="left"><code>value</code></td>
|
62 |
+
<td align="left" style="width: 25%;">
|
63 |
+
|
64 |
+
```python
|
65 |
+
str
|
66 |
+
| Path
|
67 |
+
| tuple[
|
68 |
+
str | Path,
|
69 |
+
list[tuple[int, int, int, int, str | None]],
|
70 |
+
]
|
71 |
+
| None
|
72 |
+
```
|
73 |
+
|
74 |
+
</td>
|
75 |
+
<td align="left"><code>None</code></td>
|
76 |
+
<td align="left">A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.</td>
|
77 |
+
</tr>
|
78 |
+
|
79 |
+
<tr>
|
80 |
+
<td align="left"><code>categories</code></td>
|
81 |
+
<td align="left" style="width: 25%;">
|
82 |
+
|
83 |
+
```python
|
84 |
+
list[str] | None
|
85 |
+
```
|
86 |
+
|
87 |
+
</td>
|
88 |
+
<td align="left"><code>None</code></td>
|
89 |
+
<td align="left">a list of categories to choose from when annotating the image.</td>
|
90 |
+
</tr>
|
91 |
+
|
92 |
+
<tr>
|
93 |
+
<td align="left"><code>label</code></td>
|
94 |
+
<td align="left" style="width: 25%;">
|
95 |
+
|
96 |
+
```python
|
97 |
+
str | None
|
98 |
+
```
|
99 |
+
|
100 |
+
</td>
|
101 |
+
<td align="left"><code>None</code></td>
|
102 |
+
<td align="left">the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.</td>
|
103 |
+
</tr>
|
104 |
+
|
105 |
+
<tr>
|
106 |
+
<td align="left"><code>every</code></td>
|
107 |
+
<td align="left" style="width: 25%;">
|
108 |
+
|
109 |
+
```python
|
110 |
+
Timer | float | None
|
111 |
+
```
|
112 |
+
|
113 |
+
</td>
|
114 |
+
<td align="left"><code>None</code></td>
|
115 |
+
<td align="left">Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.</td>
|
116 |
+
</tr>
|
117 |
+
|
118 |
+
<tr>
|
119 |
+
<td align="left"><code>inputs</code></td>
|
120 |
+
<td align="left" style="width: 25%;">
|
121 |
+
|
122 |
+
```python
|
123 |
+
Component | Sequence[Component] | set[Component] | None
|
124 |
+
```
|
125 |
+
|
126 |
+
</td>
|
127 |
+
<td align="left"><code>None</code></td>
|
128 |
+
<td align="left">Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.</td>
|
129 |
+
</tr>
|
130 |
+
|
131 |
+
<tr>
|
132 |
+
<td align="left"><code>show_label</code></td>
|
133 |
+
<td align="left" style="width: 25%;">
|
134 |
+
|
135 |
+
```python
|
136 |
+
bool | None
|
137 |
+
```
|
138 |
+
|
139 |
+
</td>
|
140 |
+
<td align="left"><code>None</code></td>
|
141 |
+
<td align="left">if True, will display label.</td>
|
142 |
+
</tr>
|
143 |
+
|
144 |
+
<tr>
|
145 |
+
<td align="left"><code>show_download_button</code></td>
|
146 |
+
<td align="left" style="width: 25%;">
|
147 |
+
|
148 |
+
```python
|
149 |
+
bool
|
150 |
+
```
|
151 |
+
|
152 |
+
</td>
|
153 |
+
<td align="left"><code>True</code></td>
|
154 |
+
<td align="left">If True, will display button to download annotations.</td>
|
155 |
+
</tr>
|
156 |
+
|
157 |
+
<tr>
|
158 |
+
<td align="left"><code>container</code></td>
|
159 |
+
<td align="left" style="width: 25%;">
|
160 |
+
|
161 |
+
```python
|
162 |
+
bool
|
163 |
+
```
|
164 |
+
|
165 |
+
</td>
|
166 |
+
<td align="left"><code>True</code></td>
|
167 |
+
<td align="left">If True, will place the component in a container - providing some extra padding around the border.</td>
|
168 |
+
</tr>
|
169 |
+
|
170 |
+
<tr>
|
171 |
+
<td align="left"><code>scale</code></td>
|
172 |
+
<td align="left" style="width: 25%;">
|
173 |
+
|
174 |
+
```python
|
175 |
+
int | None
|
176 |
+
```
|
177 |
+
|
178 |
+
</td>
|
179 |
+
<td align="left"><code>None</code></td>
|
180 |
+
<td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
|
181 |
+
</tr>
|
182 |
+
|
183 |
+
<tr>
|
184 |
+
<td align="left"><code>min_width</code></td>
|
185 |
+
<td align="left" style="width: 25%;">
|
186 |
+
|
187 |
+
```python
|
188 |
+
int
|
189 |
+
```
|
190 |
+
|
191 |
+
</td>
|
192 |
+
<td align="left"><code>160</code></td>
|
193 |
+
<td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
|
194 |
+
</tr>
|
195 |
+
|
196 |
+
<tr>
|
197 |
+
<td align="left"><code>interactive</code></td>
|
198 |
+
<td align="left" style="width: 25%;">
|
199 |
+
|
200 |
+
```python
|
201 |
+
bool | None
|
202 |
+
```
|
203 |
+
|
204 |
+
</td>
|
205 |
+
<td align="left"><code>None</code></td>
|
206 |
+
<td align="left">if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.</td>
|
207 |
+
</tr>
|
208 |
+
|
209 |
+
<tr>
|
210 |
+
<td align="left"><code>visible</code></td>
|
211 |
+
<td align="left" style="width: 25%;">
|
212 |
+
|
213 |
+
```python
|
214 |
+
bool
|
215 |
+
```
|
216 |
+
|
217 |
+
</td>
|
218 |
+
<td align="left"><code>True</code></td>
|
219 |
+
<td align="left">If False, component will be hidden.</td>
|
220 |
+
</tr>
|
221 |
+
|
222 |
+
<tr>
|
223 |
+
<td align="left"><code>elem_id</code></td>
|
224 |
+
<td align="left" style="width: 25%;">
|
225 |
+
|
226 |
+
```python
|
227 |
+
str | None
|
228 |
+
```
|
229 |
+
|
230 |
+
</td>
|
231 |
+
<td align="left"><code>None</code></td>
|
232 |
+
<td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
|
233 |
+
</tr>
|
234 |
+
|
235 |
+
<tr>
|
236 |
+
<td align="left"><code>elem_classes</code></td>
|
237 |
+
<td align="left" style="width: 25%;">
|
238 |
+
|
239 |
+
```python
|
240 |
+
list[str] | str | None
|
241 |
+
```
|
242 |
+
|
243 |
+
</td>
|
244 |
+
<td align="left"><code>None</code></td>
|
245 |
+
<td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
|
246 |
+
</tr>
|
247 |
+
|
248 |
+
<tr>
|
249 |
+
<td align="left"><code>render</code></td>
|
250 |
+
<td align="left" style="width: 25%;">
|
251 |
+
|
252 |
+
```python
|
253 |
+
bool
|
254 |
+
```
|
255 |
+
|
256 |
+
</td>
|
257 |
+
<td align="left"><code>True</code></td>
|
258 |
+
<td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
|
259 |
+
</tr>
|
260 |
+
|
261 |
+
<tr>
|
262 |
+
<td align="left"><code>key</code></td>
|
263 |
+
<td align="left" style="width: 25%;">
|
264 |
+
|
265 |
+
```python
|
266 |
+
int | str | None
|
267 |
+
```
|
268 |
+
|
269 |
+
</td>
|
270 |
+
<td align="left"><code>None</code></td>
|
271 |
+
<td align="left">if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.</td>
|
272 |
+
</tr>
|
273 |
+
</tbody></table>
|
274 |
+
|
275 |
+
|
276 |
+
### Events
|
277 |
+
|
278 |
+
| name | description |
|
279 |
+
|:-----|:------------|
|
280 |
+
| `clear` | This listener is triggered when the user clears the BBoxAnnotator using the clear button for the component. |
|
281 |
+
| `change` | Triggered when the value of the BBoxAnnotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
|
282 |
+
| `upload` | This listener is triggered when the user uploads a file into the BBoxAnnotator. |
|
283 |
+
|
284 |
+
|
285 |
+
|
286 |
+
### User function
|
287 |
+
|
288 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
289 |
+
|
290 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
291 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
292 |
+
|
293 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
294 |
+
|
295 |
+
- **As output:** Is passed, a tuple of `str` containing the path to the image and a list of annotations.
|
296 |
+
- **As input:** Should return, expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of.
|
297 |
+
|
298 |
+
```python
|
299 |
+
def predict(
|
300 |
+
value: tuple[str, list[tuple[int, int, int, int, str | None]]]
|
301 |
+
| None
|
302 |
+
) -> str
|
303 |
+
| pathlib.Path
|
304 |
+
| tuple[
|
305 |
+
str | pathlib.Path,
|
306 |
+
list[tuple[int, int, int, int, str | None]],
|
307 |
+
]
|
308 |
+
| None:
|
309 |
+
return value
|
310 |
+
```
|
311 |
+
|
src/backend/gradio_bbox_annotator/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from .bbox_annotator import BBoxAnnotator
|
3 |
+
|
4 |
+
__all__ = ['BBoxAnnotator']
|
src/backend/gradio_bbox_annotator/bbox_annotator.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""gr.BboxAnnotator() component."""
|
2 |
+
|
3 |
+
from __future__ import annotations
|
4 |
+
|
5 |
+
from collections.abc import Sequence
|
6 |
+
from pathlib import Path
|
7 |
+
from typing import TYPE_CHECKING, Any
|
8 |
+
|
9 |
+
from gradio_client import handle_file
|
10 |
+
from gradio_client.documentation import document
|
11 |
+
|
12 |
+
from gradio.components.base import Component
|
13 |
+
from gradio.data_classes import FileData, GradioModel
|
14 |
+
from gradio.events import Events
|
15 |
+
|
16 |
+
if TYPE_CHECKING:
|
17 |
+
from gradio.components import Timer
|
18 |
+
|
19 |
+
|
20 |
+
EXAMPLE_IMAGE_URL = (
|
21 |
+
"https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"
|
22 |
+
)
|
23 |
+
|
24 |
+
|
25 |
+
class Annotation(GradioModel):
|
26 |
+
"""Bounding box annotation data."""
|
27 |
+
left: int
|
28 |
+
top: int
|
29 |
+
right: int
|
30 |
+
bottom: int
|
31 |
+
label: str | None
|
32 |
+
|
33 |
+
@property
|
34 |
+
def width(self) -> int:
|
35 |
+
return self.right - self.left
|
36 |
+
|
37 |
+
@property
|
38 |
+
def height(self) -> int:
|
39 |
+
return self.bottom - self.top
|
40 |
+
|
41 |
+
|
42 |
+
class AnnotatedImage(GradioModel):
|
43 |
+
"""Annotated image data."""
|
44 |
+
image: FileData
|
45 |
+
annotations: list[Annotation]
|
46 |
+
|
47 |
+
|
48 |
+
@document()
|
49 |
+
class BBoxAnnotator(Component):
|
50 |
+
"""
|
51 |
+
Creates an image component that can be used to upload images with bounding box annotations (as an input)
|
52 |
+
or display images with bounding box annotations (as an output). This component can be used to annotate
|
53 |
+
images.
|
54 |
+
"""
|
55 |
+
|
56 |
+
EVENTS = [
|
57 |
+
Events.clear,
|
58 |
+
Events.change,
|
59 |
+
Events.upload,
|
60 |
+
]
|
61 |
+
|
62 |
+
data_model = AnnotatedImage
|
63 |
+
|
64 |
+
def __init__(
|
65 |
+
self,
|
66 |
+
value: str
|
67 |
+
| Path
|
68 |
+
| tuple[str | Path, list[tuple[int, int, int, int, str | None]]]
|
69 |
+
| None = None,
|
70 |
+
*,
|
71 |
+
categories: list[str] | None = None,
|
72 |
+
label: str | None = None,
|
73 |
+
every: Timer | float | None = None,
|
74 |
+
inputs: Component | Sequence[Component] | set[Component] | None = None,
|
75 |
+
show_label: bool | None = None,
|
76 |
+
show_download_button: bool = True,
|
77 |
+
container: bool = True,
|
78 |
+
scale: int | None = None,
|
79 |
+
min_width: int = 160,
|
80 |
+
interactive: bool | None = None,
|
81 |
+
visible: bool = True,
|
82 |
+
elem_id: str | None = None,
|
83 |
+
elem_classes: list[str] | str | None = None,
|
84 |
+
render: bool = True,
|
85 |
+
key: int | str | None = None,
|
86 |
+
):
|
87 |
+
"""
|
88 |
+
Parameters:
|
89 |
+
value: A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.
|
90 |
+
categories: a list of categories to choose from when annotating the image.
|
91 |
+
label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
|
92 |
+
every: Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
|
93 |
+
inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
|
94 |
+
show_label: if True, will display label.
|
95 |
+
show_download_button: If True, will display button to download annotations.
|
96 |
+
container: If True, will place the component in a container - providing some extra padding around the border.
|
97 |
+
scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
|
98 |
+
min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
|
99 |
+
interactive: if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.
|
100 |
+
visible: If False, component will be hidden.
|
101 |
+
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
|
102 |
+
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
|
103 |
+
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
|
104 |
+
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
|
105 |
+
"""
|
106 |
+
self.show_download_button = show_download_button
|
107 |
+
self.categories = categories or []
|
108 |
+
super().__init__(
|
109 |
+
label=label,
|
110 |
+
every=every,
|
111 |
+
inputs=inputs,
|
112 |
+
show_label=show_label,
|
113 |
+
container=container,
|
114 |
+
scale=scale,
|
115 |
+
min_width=min_width,
|
116 |
+
interactive=interactive,
|
117 |
+
visible=visible,
|
118 |
+
elem_id=elem_id,
|
119 |
+
elem_classes=elem_classes,
|
120 |
+
render=render,
|
121 |
+
key=key,
|
122 |
+
value=value,
|
123 |
+
)
|
124 |
+
|
125 |
+
def preprocess(
|
126 |
+
self, payload: AnnotatedImage | FileData | None
|
127 |
+
) -> tuple[str, list[tuple[int, int, int, int, str | None]]] | None:
|
128 |
+
"""
|
129 |
+
Parameters:
|
130 |
+
payload: An AnnotatedImage or FileData object containing the image and annotations.
|
131 |
+
Returns:
|
132 |
+
A tuple of `str` containing the path to the image and a list of annotations.
|
133 |
+
"""
|
134 |
+
if payload is None:
|
135 |
+
return None
|
136 |
+
elif isinstance(payload, FileData):
|
137 |
+
return (payload.path, [])
|
138 |
+
return (
|
139 |
+
payload.image.path,
|
140 |
+
[(a.left, a.top, a.right, a.bottom, a.label) for a in payload.annotations],
|
141 |
+
)
|
142 |
+
|
143 |
+
def postprocess(
|
144 |
+
self,
|
145 |
+
value: str
|
146 |
+
| Path
|
147 |
+
| tuple[str | Path, list[tuple[int, int, int, int, str | None]]]
|
148 |
+
| None,
|
149 |
+
) -> AnnotatedImage | None:
|
150 |
+
"""
|
151 |
+
Parameters:
|
152 |
+
value: Expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of
|
153 |
+
the image and a list of annotations.
|
154 |
+
Returns:
|
155 |
+
An AnnotatedImage object containing the image and annotation data.
|
156 |
+
"""
|
157 |
+
if value is None:
|
158 |
+
return None
|
159 |
+
elif isinstance(value, (str, Path)):
|
160 |
+
return AnnotatedImage(
|
161 |
+
image=FileData(path=str(value), orig_name=Path(value).name),
|
162 |
+
annotations=[],
|
163 |
+
)
|
164 |
+
return AnnotatedImage(
|
165 |
+
image=FileData(path=str(value[0]), orig_name=Path(value[0]).name),
|
166 |
+
annotations=[
|
167 |
+
Annotation(left=x[0], top=x[1], right=x[2], bottom=x[3], label=x[4])
|
168 |
+
for x in value[1]
|
169 |
+
],
|
170 |
+
)
|
171 |
+
|
172 |
+
def example_payload(self) -> Any:
|
173 |
+
return AnnotatedImage(
|
174 |
+
image=handle_file(EXAMPLE_IMAGE_URL),
|
175 |
+
annotations=[
|
176 |
+
Annotation(left=0, top=0, right=60, bottom=67, label="bus"),
|
177 |
+
Annotation(left=29, top=52, right=36, bottom=63, label="wheel"),
|
178 |
+
Annotation(left=50, top=41, right=56, bottom=53, label="wheel"),
|
179 |
+
],
|
180 |
+
)
|
181 |
+
|
182 |
+
def example_value(self) -> Any:
|
183 |
+
return (EXAMPLE_IMAGE_URL, [
|
184 |
+
(0, 0, 60, 67, "bus"),
|
185 |
+
(29, 52, 36, 63, "wheel"),
|
186 |
+
(50, 41, 56, 53, "wheel"),
|
187 |
+
])
|
src/backend/gradio_bbox_annotator/bbox_annotator.pyi
ADDED
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""gr.BBoxAnnotator() component."""
|
2 |
+
|
3 |
+
from __future__ import annotations
|
4 |
+
|
5 |
+
from collections.abc import Sequence
|
6 |
+
from pathlib import Path
|
7 |
+
from typing import TYPE_CHECKING, Any
|
8 |
+
|
9 |
+
from gradio_client import handle_file
|
10 |
+
from gradio_client.documentation import document
|
11 |
+
|
12 |
+
from gradio.components.base import Component
|
13 |
+
from gradio.data_classes import FileData, GradioModel
|
14 |
+
from gradio.events import Events
|
15 |
+
|
16 |
+
if TYPE_CHECKING:
|
17 |
+
from gradio.components import Timer
|
18 |
+
|
19 |
+
|
20 |
+
EXAMPLE_IMAGE_URL = (
|
21 |
+
"https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"
|
22 |
+
)
|
23 |
+
|
24 |
+
|
25 |
+
class Annotation(GradioModel):
|
26 |
+
"""Bounding box annotation data."""
|
27 |
+
left: int
|
28 |
+
top: int
|
29 |
+
right: int
|
30 |
+
bottom: int
|
31 |
+
label: str | None
|
32 |
+
|
33 |
+
@property
|
34 |
+
def width(self) -> int:
|
35 |
+
return self.right - self.left
|
36 |
+
|
37 |
+
@property
|
38 |
+
def height(self) -> int:
|
39 |
+
return self.bottom - self.top
|
40 |
+
|
41 |
+
|
42 |
+
class AnnotatedImage(GradioModel):
|
43 |
+
"""Annotated image data."""
|
44 |
+
image: FileData
|
45 |
+
annotations: list[Annotation]
|
46 |
+
|
47 |
+
from gradio.events import Dependency
|
48 |
+
|
49 |
+
@document()
|
50 |
+
class BBoxAnnotator(Component):
|
51 |
+
"""
|
52 |
+
Creates an image component that can be used to upload images with bounding box annotations (as an input)
|
53 |
+
or display images with bounding box annotations (as an output). This component can be used to annotate
|
54 |
+
images.
|
55 |
+
"""
|
56 |
+
|
57 |
+
EVENTS = [
|
58 |
+
Events.clear,
|
59 |
+
Events.change,
|
60 |
+
Events.upload,
|
61 |
+
]
|
62 |
+
|
63 |
+
data_model = AnnotatedImage
|
64 |
+
|
65 |
+
def __init__(
|
66 |
+
self,
|
67 |
+
value: str
|
68 |
+
| Path
|
69 |
+
| tuple[str | Path, list[tuple[int, int, int, int, str | None]]]
|
70 |
+
| None = None,
|
71 |
+
*,
|
72 |
+
categories: list[str] | None = None,
|
73 |
+
label: str | None = None,
|
74 |
+
every: Timer | float | None = None,
|
75 |
+
inputs: Component | Sequence[Component] | set[Component] | None = None,
|
76 |
+
show_label: bool | None = None,
|
77 |
+
show_download_button: bool = True,
|
78 |
+
container: bool = True,
|
79 |
+
scale: int | None = None,
|
80 |
+
min_width: int = 160,
|
81 |
+
interactive: bool | None = None,
|
82 |
+
visible: bool = True,
|
83 |
+
elem_id: str | None = None,
|
84 |
+
elem_classes: list[str] | str | None = None,
|
85 |
+
render: bool = True,
|
86 |
+
key: int | str | None = None,
|
87 |
+
):
|
88 |
+
"""
|
89 |
+
Parameters:
|
90 |
+
value: A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.
|
91 |
+
categories: a list of categories to choose from when annotating the image.
|
92 |
+
label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
|
93 |
+
every: Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
|
94 |
+
inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
|
95 |
+
show_label: if True, will display label.
|
96 |
+
show_download_button: If True, will display button to download annotations.
|
97 |
+
container: If True, will place the component in a container - providing some extra padding around the border.
|
98 |
+
scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
|
99 |
+
min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
|
100 |
+
interactive: if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.
|
101 |
+
visible: If False, component will be hidden.
|
102 |
+
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
|
103 |
+
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
|
104 |
+
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
|
105 |
+
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
|
106 |
+
"""
|
107 |
+
self.show_download_button = show_download_button
|
108 |
+
self.categories = categories or []
|
109 |
+
super().__init__(
|
110 |
+
label=label,
|
111 |
+
every=every,
|
112 |
+
inputs=inputs,
|
113 |
+
show_label=show_label,
|
114 |
+
container=container,
|
115 |
+
scale=scale,
|
116 |
+
min_width=min_width,
|
117 |
+
interactive=interactive,
|
118 |
+
visible=visible,
|
119 |
+
elem_id=elem_id,
|
120 |
+
elem_classes=elem_classes,
|
121 |
+
render=render,
|
122 |
+
key=key,
|
123 |
+
value=value,
|
124 |
+
)
|
125 |
+
|
126 |
+
def preprocess(
|
127 |
+
self, payload: AnnotatedImage | FileData | None
|
128 |
+
) -> tuple[str, list[tuple[int, int, int, int, str | None]]] | None:
|
129 |
+
"""
|
130 |
+
Parameters:
|
131 |
+
payload: An AnnotatedImage or FileData object containing the image and annotations.
|
132 |
+
Returns:
|
133 |
+
A tuple of `str` containing the path to the image and a list of annotations.
|
134 |
+
"""
|
135 |
+
if payload is None:
|
136 |
+
return None
|
137 |
+
elif isinstance(payload, FileData):
|
138 |
+
return (payload.path, [])
|
139 |
+
return (
|
140 |
+
payload.image.path,
|
141 |
+
[(a.left, a.top, a.right, a.bottom, a.label) for a in payload.annotations],
|
142 |
+
)
|
143 |
+
|
144 |
+
def postprocess(
|
145 |
+
self,
|
146 |
+
value: str
|
147 |
+
| Path
|
148 |
+
| tuple[str | Path, list[tuple[int, int, int, int, str | None]]]
|
149 |
+
| None,
|
150 |
+
) -> AnnotatedImage | None:
|
151 |
+
"""
|
152 |
+
Parameters:
|
153 |
+
value: Expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of
|
154 |
+
the image and a list of annotations.
|
155 |
+
Returns:
|
156 |
+
An AnnotatedImage object containing the image and annotation data.
|
157 |
+
"""
|
158 |
+
if value is None:
|
159 |
+
return None
|
160 |
+
elif isinstance(value, (str, Path)):
|
161 |
+
return AnnotatedImage(
|
162 |
+
image=FileData(path=str(value), orig_name=Path(value).name),
|
163 |
+
annotations=[],
|
164 |
+
)
|
165 |
+
return AnnotatedImage(
|
166 |
+
image=FileData(path=str(value[0]), orig_name=Path(value[0]).name),
|
167 |
+
annotations=[
|
168 |
+
Annotation(left=x[0], top=x[1], right=x[2], bottom=x[3], label=x[4])
|
169 |
+
for x in value[1]
|
170 |
+
],
|
171 |
+
)
|
172 |
+
|
173 |
+
def example_payload(self) -> Any:
|
174 |
+
return AnnotatedImage(
|
175 |
+
image=handle_file(EXAMPLE_IMAGE_URL),
|
176 |
+
annotations=[
|
177 |
+
Annotation(left=0, top=0, right=60, bottom=67, label="bus"),
|
178 |
+
Annotation(left=29, top=52, right=36, bottom=63, label="wheel"),
|
179 |
+
Annotation(left=50, top=41, right=56, bottom=53, label="wheel"),
|
180 |
+
],
|
181 |
+
)
|
182 |
+
|
183 |
+
def example_value(self) -> Any:
|
184 |
+
return (EXAMPLE_IMAGE_URL, [
|
185 |
+
(0, 0, 60, 67, "bus"),
|
186 |
+
(29, 52, 36, 63, "wheel"),
|
187 |
+
(50, 41, 56, 53, "wheel"),
|
188 |
+
])
|
189 |
+
from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
|
190 |
+
from gradio.blocks import Block
|
191 |
+
if TYPE_CHECKING:
|
192 |
+
from gradio.components import Timer
|
193 |
+
|
194 |
+
|
195 |
+
def clear(self,
|
196 |
+
fn: Callable[..., Any] | None = None,
|
197 |
+
inputs: Block | Sequence[Block] | set[Block] | None = None,
|
198 |
+
outputs: Block | Sequence[Block] | None = None,
|
199 |
+
api_name: str | None | Literal[False] = None,
|
200 |
+
scroll_to_output: bool = False,
|
201 |
+
show_progress: Literal["full", "minimal", "hidden"] = "full",
|
202 |
+
queue: bool | None = None,
|
203 |
+
batch: bool = False,
|
204 |
+
max_batch_size: int = 4,
|
205 |
+
preprocess: bool = True,
|
206 |
+
postprocess: bool = True,
|
207 |
+
cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
|
208 |
+
every: Timer | float | None = None,
|
209 |
+
trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
|
210 |
+
js: str | None = None,
|
211 |
+
concurrency_limit: int | None | Literal["default"] = "default",
|
212 |
+
concurrency_id: str | None = None,
|
213 |
+
show_api: bool = True,
|
214 |
+
|
215 |
+
) -> Dependency:
|
216 |
+
"""
|
217 |
+
Parameters:
|
218 |
+
fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component.
|
219 |
+
inputs: list of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list.
|
220 |
+
outputs: list of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list.
|
221 |
+
api_name: defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name.
|
222 |
+
scroll_to_output: if True, will scroll to output component on completion
|
223 |
+
show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
|
224 |
+
queue: if True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app.
|
225 |
+
batch: if True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
|
226 |
+
max_batch_size: maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
|
227 |
+
preprocess: if False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
|
228 |
+
postprocess: if False, will not run postprocessing of component data before returning 'fn' output to the browser.
|
229 |
+
cancels: a list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish.
|
230 |
+
every: continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
|
231 |
+
trigger_mode: if "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete.
|
232 |
+
js: optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
|
233 |
+
concurrency_limit: if set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default).
|
234 |
+
concurrency_id: if set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
|
235 |
+
show_api: whether to show this event in the "view API" page of the Gradio app, or in the ".view_api()" method of the Gradio clients. Unlike setting api_name to False, setting show_api to False will still allow downstream apps as well as the Clients to use this event. If fn is None, show_api will automatically be set to False.
|
236 |
+
|
237 |
+
"""
|
238 |
+
...
|
239 |
+
|
240 |
+
def change(self,
|
241 |
+
fn: Callable[..., Any] | None = None,
|
242 |
+
inputs: Block | Sequence[Block] | set[Block] | None = None,
|
243 |
+
outputs: Block | Sequence[Block] | None = None,
|
244 |
+
api_name: str | None | Literal[False] = None,
|
245 |
+
scroll_to_output: bool = False,
|
246 |
+
show_progress: Literal["full", "minimal", "hidden"] = "full",
|
247 |
+
queue: bool | None = None,
|
248 |
+
batch: bool = False,
|
249 |
+
max_batch_size: int = 4,
|
250 |
+
preprocess: bool = True,
|
251 |
+
postprocess: bool = True,
|
252 |
+
cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
|
253 |
+
every: Timer | float | None = None,
|
254 |
+
trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
|
255 |
+
js: str | None = None,
|
256 |
+
concurrency_limit: int | None | Literal["default"] = "default",
|
257 |
+
concurrency_id: str | None = None,
|
258 |
+
show_api: bool = True,
|
259 |
+
|
260 |
+
) -> Dependency:
|
261 |
+
"""
|
262 |
+
Parameters:
|
263 |
+
fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component.
|
264 |
+
inputs: list of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list.
|
265 |
+
outputs: list of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list.
|
266 |
+
api_name: defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name.
|
267 |
+
scroll_to_output: if True, will scroll to output component on completion
|
268 |
+
show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
|
269 |
+
queue: if True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app.
|
270 |
+
batch: if True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
|
271 |
+
max_batch_size: maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
|
272 |
+
preprocess: if False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
|
273 |
+
postprocess: if False, will not run postprocessing of component data before returning 'fn' output to the browser.
|
274 |
+
cancels: a list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish.
|
275 |
+
every: continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
|
276 |
+
trigger_mode: if "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete.
|
277 |
+
js: optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
|
278 |
+
concurrency_limit: if set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default).
|
279 |
+
concurrency_id: if set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
|
280 |
+
show_api: whether to show this event in the "view API" page of the Gradio app, or in the ".view_api()" method of the Gradio clients. Unlike setting api_name to False, setting show_api to False will still allow downstream apps as well as the Clients to use this event. If fn is None, show_api will automatically be set to False.
|
281 |
+
|
282 |
+
"""
|
283 |
+
...
|
284 |
+
|
285 |
+
def upload(self,
|
286 |
+
fn: Callable[..., Any] | None = None,
|
287 |
+
inputs: Block | Sequence[Block] | set[Block] | None = None,
|
288 |
+
outputs: Block | Sequence[Block] | None = None,
|
289 |
+
api_name: str | None | Literal[False] = None,
|
290 |
+
scroll_to_output: bool = False,
|
291 |
+
show_progress: Literal["full", "minimal", "hidden"] = "full",
|
292 |
+
queue: bool | None = None,
|
293 |
+
batch: bool = False,
|
294 |
+
max_batch_size: int = 4,
|
295 |
+
preprocess: bool = True,
|
296 |
+
postprocess: bool = True,
|
297 |
+
cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
|
298 |
+
every: Timer | float | None = None,
|
299 |
+
trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
|
300 |
+
js: str | None = None,
|
301 |
+
concurrency_limit: int | None | Literal["default"] = "default",
|
302 |
+
concurrency_id: str | None = None,
|
303 |
+
show_api: bool = True,
|
304 |
+
|
305 |
+
) -> Dependency:
|
306 |
+
"""
|
307 |
+
Parameters:
|
308 |
+
fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component.
|
309 |
+
inputs: list of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list.
|
310 |
+
outputs: list of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list.
|
311 |
+
api_name: defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name.
|
312 |
+
scroll_to_output: if True, will scroll to output component on completion
|
313 |
+
show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
|
314 |
+
queue: if True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app.
|
315 |
+
batch: if True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
|
316 |
+
max_batch_size: maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
|
317 |
+
preprocess: if False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
|
318 |
+
postprocess: if False, will not run postprocessing of component data before returning 'fn' output to the browser.
|
319 |
+
cancels: a list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish.
|
320 |
+
every: continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
|
321 |
+
trigger_mode: if "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete.
|
322 |
+
js: optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
|
323 |
+
concurrency_limit: if set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default).
|
324 |
+
concurrency_id: if set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
|
325 |
+
show_api: whether to show this event in the "view API" page of the Gradio app, or in the ".view_api()" method of the Gradio clients. Unlike setting api_name to False, setting show_api to False will still allow downstream apps as well as the Clients to use this event. If fn is None, show_api will automatically be set to False.
|
326 |
+
|
327 |
+
"""
|
328 |
+
...
|
src/backend/gradio_bbox_annotator/templates/component/index.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/backend/gradio_bbox_annotator/templates/component/style.css
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/backend/gradio_bbox_annotator/templates/example/index.js
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const {
|
2 |
+
SvelteComponent: h,
|
3 |
+
append_hydration: y,
|
4 |
+
attr: c,
|
5 |
+
children: b,
|
6 |
+
claim_element: u,
|
7 |
+
detach: f,
|
8 |
+
element: _,
|
9 |
+
empty: d,
|
10 |
+
init: k,
|
11 |
+
insert_hydration: v,
|
12 |
+
noop: o,
|
13 |
+
safe_not_equal: p,
|
14 |
+
src_url_equal: m,
|
15 |
+
toggle_class: r
|
16 |
+
} = window.__gradio__svelte__internal;
|
17 |
+
function g(s) {
|
18 |
+
let l, e, t;
|
19 |
+
return {
|
20 |
+
c() {
|
21 |
+
l = _("div"), e = _("img"), this.h();
|
22 |
+
},
|
23 |
+
l(i) {
|
24 |
+
l = u(i, "DIV", { class: !0 });
|
25 |
+
var a = b(l);
|
26 |
+
e = u(a, "IMG", { src: !0, alt: !0, class: !0 }), a.forEach(f), this.h();
|
27 |
+
},
|
28 |
+
h() {
|
29 |
+
m(e.src, t = /*value*/
|
30 |
+
s[0].image.url) || c(e, "src", t), c(e, "alt", ""), c(e, "class", "svelte-giydt1"), c(l, "class", "container svelte-giydt1"), r(
|
31 |
+
l,
|
32 |
+
"table",
|
33 |
+
/*type*/
|
34 |
+
s[1] === "table"
|
35 |
+
), r(
|
36 |
+
l,
|
37 |
+
"gallery",
|
38 |
+
/*type*/
|
39 |
+
s[1] === "gallery"
|
40 |
+
), r(
|
41 |
+
l,
|
42 |
+
"selected",
|
43 |
+
/*selected*/
|
44 |
+
s[2]
|
45 |
+
);
|
46 |
+
},
|
47 |
+
m(i, a) {
|
48 |
+
v(i, l, a), y(l, e);
|
49 |
+
},
|
50 |
+
p(i, a) {
|
51 |
+
a & /*value*/
|
52 |
+
1 && !m(e.src, t = /*value*/
|
53 |
+
i[0].image.url) && c(e, "src", t), a & /*type*/
|
54 |
+
2 && r(
|
55 |
+
l,
|
56 |
+
"table",
|
57 |
+
/*type*/
|
58 |
+
i[1] === "table"
|
59 |
+
), a & /*type*/
|
60 |
+
2 && r(
|
61 |
+
l,
|
62 |
+
"gallery",
|
63 |
+
/*type*/
|
64 |
+
i[1] === "gallery"
|
65 |
+
), a & /*selected*/
|
66 |
+
4 && r(
|
67 |
+
l,
|
68 |
+
"selected",
|
69 |
+
/*selected*/
|
70 |
+
i[2]
|
71 |
+
);
|
72 |
+
},
|
73 |
+
d(i) {
|
74 |
+
i && f(l);
|
75 |
+
}
|
76 |
+
};
|
77 |
+
}
|
78 |
+
function q(s) {
|
79 |
+
let l, e = (
|
80 |
+
/*value*/
|
81 |
+
s[0] && g(s)
|
82 |
+
);
|
83 |
+
return {
|
84 |
+
c() {
|
85 |
+
e && e.c(), l = d();
|
86 |
+
},
|
87 |
+
l(t) {
|
88 |
+
e && e.l(t), l = d();
|
89 |
+
},
|
90 |
+
m(t, i) {
|
91 |
+
e && e.m(t, i), v(t, l, i);
|
92 |
+
},
|
93 |
+
p(t, [i]) {
|
94 |
+
/*value*/
|
95 |
+
t[0] ? e ? e.p(t, i) : (e = g(t), e.c(), e.m(l.parentNode, l)) : e && (e.d(1), e = null);
|
96 |
+
},
|
97 |
+
i: o,
|
98 |
+
o,
|
99 |
+
d(t) {
|
100 |
+
t && f(l), e && e.d(t);
|
101 |
+
}
|
102 |
+
};
|
103 |
+
}
|
104 |
+
function w(s, l, e) {
|
105 |
+
let { value: t } = l, { type: i } = l, { selected: a = !1 } = l;
|
106 |
+
return s.$$set = (n) => {
|
107 |
+
"value" in n && e(0, t = n.value), "type" in n && e(1, i = n.type), "selected" in n && e(2, a = n.selected);
|
108 |
+
}, [t, i, a];
|
109 |
+
}
|
110 |
+
class E extends h {
|
111 |
+
constructor(l) {
|
112 |
+
super(), k(this, l, w, q, p, { value: 0, type: 1, selected: 2 });
|
113 |
+
}
|
114 |
+
}
|
115 |
+
export {
|
116 |
+
E as default
|
117 |
+
};
|
src/backend/gradio_bbox_annotator/templates/example/style.css
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
.container.svelte-giydt1 img{width:100%;height:100%}.container.selected.svelte-giydt1.svelte-giydt1{border-color:var(--border-color-accent)}.container.table.svelte-giydt1.svelte-giydt1{margin:0 auto;border:2px solid var(--border-color-primary);border-radius:var(--radius-lg);overflow:hidden;width:var(--size-20);height:var(--size-20);object-fit:cover}.container.gallery.svelte-giydt1.svelte-giydt1{height:var(--size-20);max-height:var(--size-20);object-fit:cover}.container.svelte-giydt1 img.svelte-giydt1{object-fit:cover}
|
src/demo/__init__.py
ADDED
File without changes
|
src/demo/app.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
4 |
+
|
5 |
+
|
6 |
+
example = BBoxAnnotator().example_value()
|
7 |
+
|
8 |
+
demo = gr.Interface(
|
9 |
+
lambda x: x,
|
10 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
11 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
12 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
13 |
+
)
|
14 |
+
|
15 |
+
|
16 |
+
if __name__ == "__main__":
|
17 |
+
demo.launch()
|
src/demo/css.css
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html {
|
2 |
+
font-family: Inter;
|
3 |
+
font-size: 16px;
|
4 |
+
font-weight: 400;
|
5 |
+
line-height: 1.5;
|
6 |
+
-webkit-text-size-adjust: 100%;
|
7 |
+
background: #fff;
|
8 |
+
color: #323232;
|
9 |
+
-webkit-font-smoothing: antialiased;
|
10 |
+
-moz-osx-font-smoothing: grayscale;
|
11 |
+
text-rendering: optimizeLegibility;
|
12 |
+
}
|
13 |
+
|
14 |
+
:root {
|
15 |
+
--space: 1;
|
16 |
+
--vspace: calc(var(--space) * 1rem);
|
17 |
+
--vspace-0: calc(3 * var(--space) * 1rem);
|
18 |
+
--vspace-1: calc(2 * var(--space) * 1rem);
|
19 |
+
--vspace-2: calc(1.5 * var(--space) * 1rem);
|
20 |
+
--vspace-3: calc(0.5 * var(--space) * 1rem);
|
21 |
+
}
|
22 |
+
|
23 |
+
.app {
|
24 |
+
max-width: 748px !important;
|
25 |
+
}
|
26 |
+
|
27 |
+
.prose p {
|
28 |
+
margin: var(--vspace) 0;
|
29 |
+
line-height: var(--vspace * 2);
|
30 |
+
font-size: 1rem;
|
31 |
+
}
|
32 |
+
|
33 |
+
code {
|
34 |
+
font-family: "Inconsolata", sans-serif;
|
35 |
+
font-size: 16px;
|
36 |
+
}
|
37 |
+
|
38 |
+
h1,
|
39 |
+
h1 code {
|
40 |
+
font-weight: 400;
|
41 |
+
line-height: calc(2.5 / var(--space) * var(--vspace));
|
42 |
+
}
|
43 |
+
|
44 |
+
h1 code {
|
45 |
+
background: none;
|
46 |
+
border: none;
|
47 |
+
letter-spacing: 0.05em;
|
48 |
+
padding-bottom: 5px;
|
49 |
+
position: relative;
|
50 |
+
padding: 0;
|
51 |
+
}
|
52 |
+
|
53 |
+
h2 {
|
54 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
55 |
+
line-height: 1em;
|
56 |
+
}
|
57 |
+
|
58 |
+
h3,
|
59 |
+
h3 code {
|
60 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
61 |
+
line-height: 1em;
|
62 |
+
}
|
63 |
+
|
64 |
+
h4,
|
65 |
+
h5,
|
66 |
+
h6 {
|
67 |
+
margin: var(--vspace-3) 0 var(--vspace-3) 0;
|
68 |
+
line-height: var(--vspace);
|
69 |
+
}
|
70 |
+
|
71 |
+
.bigtitle,
|
72 |
+
h1,
|
73 |
+
h1 code {
|
74 |
+
font-size: calc(8px * 4.5);
|
75 |
+
word-break: break-word;
|
76 |
+
}
|
77 |
+
|
78 |
+
.title,
|
79 |
+
h2,
|
80 |
+
h2 code {
|
81 |
+
font-size: calc(8px * 3.375);
|
82 |
+
font-weight: lighter;
|
83 |
+
word-break: break-word;
|
84 |
+
border: none;
|
85 |
+
background: none;
|
86 |
+
}
|
87 |
+
|
88 |
+
.subheading1,
|
89 |
+
h3,
|
90 |
+
h3 code {
|
91 |
+
font-size: calc(8px * 1.8);
|
92 |
+
font-weight: 600;
|
93 |
+
border: none;
|
94 |
+
background: none;
|
95 |
+
letter-spacing: 0.1em;
|
96 |
+
text-transform: uppercase;
|
97 |
+
}
|
98 |
+
|
99 |
+
h2 code {
|
100 |
+
padding: 0;
|
101 |
+
position: relative;
|
102 |
+
letter-spacing: 0.05em;
|
103 |
+
}
|
104 |
+
|
105 |
+
blockquote {
|
106 |
+
font-size: calc(8px * 1.1667);
|
107 |
+
font-style: italic;
|
108 |
+
line-height: calc(1.1667 * var(--vspace));
|
109 |
+
margin: var(--vspace-2) var(--vspace-2);
|
110 |
+
}
|
111 |
+
|
112 |
+
.subheading2,
|
113 |
+
h4 {
|
114 |
+
font-size: calc(8px * 1.4292);
|
115 |
+
text-transform: uppercase;
|
116 |
+
font-weight: 600;
|
117 |
+
}
|
118 |
+
|
119 |
+
.subheading3,
|
120 |
+
h5 {
|
121 |
+
font-size: calc(8px * 1.2917);
|
122 |
+
line-height: calc(1.2917 * var(--vspace));
|
123 |
+
|
124 |
+
font-weight: lighter;
|
125 |
+
text-transform: uppercase;
|
126 |
+
letter-spacing: 0.15em;
|
127 |
+
}
|
128 |
+
|
129 |
+
h6 {
|
130 |
+
font-size: calc(8px * 1.1667);
|
131 |
+
font-size: 1.1667em;
|
132 |
+
font-weight: normal;
|
133 |
+
font-style: italic;
|
134 |
+
font-family: "le-monde-livre-classic-byol", serif !important;
|
135 |
+
letter-spacing: 0px !important;
|
136 |
+
}
|
137 |
+
|
138 |
+
#start .md > *:first-child {
|
139 |
+
margin-top: 0;
|
140 |
+
}
|
141 |
+
|
142 |
+
h2 + h3 {
|
143 |
+
margin-top: 0;
|
144 |
+
}
|
145 |
+
|
146 |
+
.md hr {
|
147 |
+
border: none;
|
148 |
+
border-top: 1px solid var(--block-border-color);
|
149 |
+
margin: var(--vspace-2) 0 var(--vspace-2) 0;
|
150 |
+
}
|
151 |
+
.prose ul {
|
152 |
+
margin: var(--vspace-2) 0 var(--vspace-1) 0;
|
153 |
+
}
|
154 |
+
|
155 |
+
.gap {
|
156 |
+
gap: 0;
|
157 |
+
}
|
src/demo/requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio_bbox_annotator
|
src/demo/space.py
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from app import demo as app
|
4 |
+
import os
|
5 |
+
|
6 |
+
_docs = {'BBoxAnnotator': {'description': 'Creates an image component that can be used to upload images with bounding box annotations (as an input)\nor display images with bounding box annotations (as an output). This component can be used to annotate\nimages.', 'members': {'__init__': {'value': {'type': 'str\n | Path\n | tuple[\n str | Path,\n list[tuple[int, int, int, int, str | None]],\n ]\n | None', 'default': 'None', 'description': 'A path or URL for the image, or a tuple of the image and list of (left, top, right, bottom, label) annotations.'}, 'categories': {'type': 'list[str] | None', 'default': 'None', 'description': 'a list of categories to choose from when annotating the image.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will display button to download annotations.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | None', 'default': 'None', 'description': 'if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.'}}, 'postprocess': {'value': {'type': 'str\n | pathlib.Path\n | tuple[\n str | pathlib.Path,\n list[tuple[int, int, int, int, str | None]],\n ]\n | None', 'description': 'Expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of'}}, 'preprocess': {'return': {'type': 'tuple[str, list[tuple[int, int, int, int, str | None]]]\n | None', 'description': 'A tuple of `str` containing the path to the image and a list of annotations.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the BBoxAnnotator using the clear button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the BBoxAnnotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the BBoxAnnotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'BBoxAnnotator': []}}}
|
7 |
+
|
8 |
+
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
+
|
10 |
+
with gr.Blocks(
|
11 |
+
css=abs_path,
|
12 |
+
theme=gr.themes.Default(
|
13 |
+
font_mono=[
|
14 |
+
gr.themes.GoogleFont("Inconsolata"),
|
15 |
+
"monospace",
|
16 |
+
],
|
17 |
+
),
|
18 |
+
) as demo:
|
19 |
+
gr.Markdown(
|
20 |
+
"""
|
21 |
+
# `gradio_bbox_annotator`
|
22 |
+
|
23 |
+
<div style="display: flex; gap: 7px;">
|
24 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.1.0%20-%20orange"> <a href="https://github.com/kyamagu/gradio-bbox-annotator/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
Bounding box annotation tool
|
28 |
+
""", elem_classes=["md-custom"], header_links=True)
|
29 |
+
app.render()
|
30 |
+
gr.Markdown(
|
31 |
+
"""
|
32 |
+
## Installation
|
33 |
+
|
34 |
+
```bash
|
35 |
+
pip install gradio_bbox_annotator
|
36 |
+
```
|
37 |
+
|
38 |
+
## Usage
|
39 |
+
|
40 |
+
```python
|
41 |
+
|
42 |
+
import gradio as gr
|
43 |
+
from gradio_bbox_annotator import BBoxAnnotator
|
44 |
+
|
45 |
+
|
46 |
+
example = BBoxAnnotator().example_value()
|
47 |
+
|
48 |
+
demo = gr.Interface(
|
49 |
+
lambda x: x,
|
50 |
+
BBoxAnnotator(value=example, show_label=False), # interactive version of your component
|
51 |
+
BBoxAnnotator(show_label=False), # static version of your component
|
52 |
+
examples=[[example]], # uncomment this line to view the "example version" of your component
|
53 |
+
)
|
54 |
+
|
55 |
+
|
56 |
+
if __name__ == "__main__":
|
57 |
+
demo.launch()
|
58 |
+
|
59 |
+
```
|
60 |
+
""", elem_classes=["md-custom"], header_links=True)
|
61 |
+
|
62 |
+
|
63 |
+
gr.Markdown("""
|
64 |
+
## `BBoxAnnotator`
|
65 |
+
|
66 |
+
### Initialization
|
67 |
+
""", elem_classes=["md-custom"], header_links=True)
|
68 |
+
|
69 |
+
gr.ParamViewer(value=_docs["BBoxAnnotator"]["members"]["__init__"], linkify=[])
|
70 |
+
|
71 |
+
|
72 |
+
gr.Markdown("### Events")
|
73 |
+
gr.ParamViewer(value=_docs["BBoxAnnotator"]["events"], linkify=['Event'])
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
gr.Markdown("""
|
79 |
+
|
80 |
+
### User function
|
81 |
+
|
82 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
83 |
+
|
84 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
85 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
86 |
+
|
87 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
88 |
+
|
89 |
+
- **As input:** Is passed, a tuple of `str` containing the path to the image and a list of annotations.
|
90 |
+
- **As output:** Should return, expects a `str` or `pathlib.Path` object containing the path to the image, or a tuple of.
|
91 |
+
|
92 |
+
```python
|
93 |
+
def predict(
|
94 |
+
value: tuple[str, list[tuple[int, int, int, int, str | None]]]
|
95 |
+
| None
|
96 |
+
) -> str
|
97 |
+
| pathlib.Path
|
98 |
+
| tuple[
|
99 |
+
str | pathlib.Path,
|
100 |
+
list[tuple[int, int, int, int, str | None]],
|
101 |
+
]
|
102 |
+
| None:
|
103 |
+
return value
|
104 |
+
```
|
105 |
+
""", elem_classes=["md-custom", "BBoxAnnotator-user-fn"], header_links=True)
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
demo.load(None, js=r"""function() {
|
111 |
+
const refs = {};
|
112 |
+
const user_fn_refs = {
|
113 |
+
BBoxAnnotator: [], };
|
114 |
+
requestAnimationFrame(() => {
|
115 |
+
|
116 |
+
Object.entries(user_fn_refs).forEach(([key, refs]) => {
|
117 |
+
if (refs.length > 0) {
|
118 |
+
const el = document.querySelector(`.${key}-user-fn`);
|
119 |
+
if (!el) return;
|
120 |
+
refs.forEach(ref => {
|
121 |
+
el.innerHTML = el.innerHTML.replace(
|
122 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
123 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
124 |
+
);
|
125 |
+
})
|
126 |
+
}
|
127 |
+
})
|
128 |
+
|
129 |
+
Object.entries(refs).forEach(([key, refs]) => {
|
130 |
+
if (refs.length > 0) {
|
131 |
+
const el = document.querySelector(`.${key}`);
|
132 |
+
if (!el) return;
|
133 |
+
refs.forEach(ref => {
|
134 |
+
el.innerHTML = el.innerHTML.replace(
|
135 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
136 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
137 |
+
);
|
138 |
+
})
|
139 |
+
}
|
140 |
+
})
|
141 |
+
})
|
142 |
+
}
|
143 |
+
|
144 |
+
""")
|
145 |
+
|
146 |
+
demo.launch()
|
src/frontend/Example.svelte
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { AnnotatedImage } from "./shared/utils";
|
3 |
+
|
4 |
+
export let value: null | AnnotatedImage;
|
5 |
+
export let type: "gallery" | "table";
|
6 |
+
export let selected = false;
|
7 |
+
</script>
|
8 |
+
|
9 |
+
{#if value}
|
10 |
+
<div
|
11 |
+
class="container"
|
12 |
+
class:table={type === "table"}
|
13 |
+
class:gallery={type === "gallery"}
|
14 |
+
class:selected
|
15 |
+
>
|
16 |
+
<img src={value.image.url} alt="" />
|
17 |
+
</div>
|
18 |
+
{/if}
|
19 |
+
|
20 |
+
<style>
|
21 |
+
.container :global(img) {
|
22 |
+
width: 100%;
|
23 |
+
height: 100%;
|
24 |
+
}
|
25 |
+
|
26 |
+
.container.selected {
|
27 |
+
border-color: var(--border-color-accent);
|
28 |
+
}
|
29 |
+
|
30 |
+
.container.table {
|
31 |
+
margin: 0 auto;
|
32 |
+
border: 2px solid var(--border-color-primary);
|
33 |
+
border-radius: var(--radius-lg);
|
34 |
+
overflow: hidden;
|
35 |
+
width: var(--size-20);
|
36 |
+
height: var(--size-20);
|
37 |
+
object-fit: cover;
|
38 |
+
}
|
39 |
+
|
40 |
+
.container.gallery {
|
41 |
+
height: var(--size-20);
|
42 |
+
max-height: var(--size-20);
|
43 |
+
object-fit: cover;
|
44 |
+
}
|
45 |
+
.container img {
|
46 |
+
object-fit: cover;
|
47 |
+
}
|
48 |
+
</style>
|
src/frontend/Index.svelte
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<svelte:options accessors={true} />
|
2 |
+
|
3 |
+
<script context="module" lang="ts">
|
4 |
+
export { default as BaseImageUploader } from "./shared/ImageUploader.svelte";
|
5 |
+
export { default as BaseStaticImage } from "./shared/ImagePreview.svelte";
|
6 |
+
export { default as BaseExample } from "./Example.svelte";
|
7 |
+
</script>
|
8 |
+
|
9 |
+
<script lang="ts">
|
10 |
+
import type { Gradio } from "@gradio/utils";
|
11 |
+
import ImagePreview from "./shared/ImagePreview.svelte";
|
12 |
+
import ImageUploader from "./shared/ImageUploader.svelte";
|
13 |
+
import type { AnnotatedImage } from "./shared/utils";
|
14 |
+
|
15 |
+
import { Block, UploadText } from "@gradio/atoms";
|
16 |
+
import { StatusTracker } from "@gradio/statustracker";
|
17 |
+
import type { LoadingStatus } from "@gradio/statustracker";
|
18 |
+
|
19 |
+
export let elem_id = "";
|
20 |
+
export let elem_classes: string[] = [];
|
21 |
+
export let visible = true;
|
22 |
+
export let value: null | AnnotatedImage = null;
|
23 |
+
export let label: string;
|
24 |
+
export let show_label: boolean;
|
25 |
+
export let show_download_button: boolean;
|
26 |
+
export let container = true;
|
27 |
+
export let scale: number | null = null;
|
28 |
+
export let min_width: number | undefined = undefined;
|
29 |
+
export let loading_status: LoadingStatus;
|
30 |
+
export let interactive: boolean;
|
31 |
+
export let root: string;
|
32 |
+
export let placeholder: string | undefined = undefined;
|
33 |
+
export let categories: string[] = [];
|
34 |
+
|
35 |
+
export let gradio: Gradio<{
|
36 |
+
change: never;
|
37 |
+
upload: never;
|
38 |
+
clear: never;
|
39 |
+
clear_status: LoadingStatus;
|
40 |
+
}>;
|
41 |
+
|
42 |
+
$: value, gradio.dispatch("change");
|
43 |
+
|
44 |
+
let dragging: boolean;
|
45 |
+
</script>
|
46 |
+
|
47 |
+
{#if !interactive}
|
48 |
+
<Block
|
49 |
+
{visible}
|
50 |
+
variant={"solid"}
|
51 |
+
border_mode={dragging ? "focus" : "base"}
|
52 |
+
padding={false}
|
53 |
+
{elem_id}
|
54 |
+
{elem_classes}
|
55 |
+
allow_overflow={false}
|
56 |
+
{container}
|
57 |
+
{scale}
|
58 |
+
{min_width}
|
59 |
+
>
|
60 |
+
<StatusTracker
|
61 |
+
autoscroll={gradio.autoscroll}
|
62 |
+
i18n={gradio.i18n}
|
63 |
+
{...loading_status}
|
64 |
+
on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
|
65 |
+
/>
|
66 |
+
<ImagePreview
|
67 |
+
{value}
|
68 |
+
{label}
|
69 |
+
{show_label}
|
70 |
+
{show_download_button}
|
71 |
+
i18n={gradio.i18n}
|
72 |
+
/>
|
73 |
+
</Block>
|
74 |
+
{:else}
|
75 |
+
<Block
|
76 |
+
{visible}
|
77 |
+
variant={value === null ? "dashed" : "solid"}
|
78 |
+
border_mode={dragging ? "focus" : "base"}
|
79 |
+
padding={false}
|
80 |
+
{elem_id}
|
81 |
+
{elem_classes}
|
82 |
+
allow_overflow={false}
|
83 |
+
{container}
|
84 |
+
{scale}
|
85 |
+
{min_width}
|
86 |
+
>
|
87 |
+
<StatusTracker
|
88 |
+
autoscroll={gradio.autoscroll}
|
89 |
+
i18n={gradio.i18n}
|
90 |
+
{...loading_status}
|
91 |
+
on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
|
92 |
+
/>
|
93 |
+
|
94 |
+
<ImageUploader
|
95 |
+
upload={(...args) => gradio.client.upload(...args)}
|
96 |
+
stream_handler={(...args) => gradio.client.stream(...args)}
|
97 |
+
bind:value
|
98 |
+
{root}
|
99 |
+
on:clear={() => gradio.dispatch("clear")}
|
100 |
+
on:drag={({ detail }) => (dragging = detail)}
|
101 |
+
on:upload={() => gradio.dispatch("upload")}
|
102 |
+
{label}
|
103 |
+
{show_label}
|
104 |
+
{categories}
|
105 |
+
>
|
106 |
+
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
107 |
+
</ImageUploader>
|
108 |
+
</Block>
|
109 |
+
{/if}
|
src/frontend/gradio.config.js
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import commonjs from "vite-plugin-commonjs";
|
2 |
+
|
3 |
+
export default {
|
4 |
+
plugins: [
|
5 |
+
commonjs({
|
6 |
+
filter: (id) => id.includes("node_modules/deepmerge")
|
7 |
+
})
|
8 |
+
],
|
9 |
+
svelte: {
|
10 |
+
preprocess: [],
|
11 |
+
},
|
12 |
+
build: {
|
13 |
+
target: "modules",
|
14 |
+
},
|
15 |
+
};
|
src/frontend/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/frontend/package.json
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "gradio_imageannotator",
|
3 |
+
"version": "0.8.8",
|
4 |
+
"description": "Gradio UI packages",
|
5 |
+
"type": "module",
|
6 |
+
"author": "",
|
7 |
+
"license": "ISC",
|
8 |
+
"private": false,
|
9 |
+
"dependencies": {
|
10 |
+
"@gradio/atoms": "0.11.1",
|
11 |
+
"@gradio/client": "1.8.0",
|
12 |
+
"@gradio/icons": "0.8.1",
|
13 |
+
"@gradio/statustracker": "0.9.5",
|
14 |
+
"@gradio/upload": "0.14.1",
|
15 |
+
"@gradio/utils": "0.8.0",
|
16 |
+
"@gradio/wasm": "0.15.0",
|
17 |
+
"cropperjs": "^1.5.12",
|
18 |
+
"deepmerge": "2.2.1",
|
19 |
+
"lazy-brush": "^1.0.1",
|
20 |
+
"resize-observer-polyfill": "^1.5.1",
|
21 |
+
"svelte-i18n": "^4.0.1"
|
22 |
+
},
|
23 |
+
"devDependencies": {
|
24 |
+
"@gradio/preview": "^0.13.0",
|
25 |
+
"@tsconfig/svelte": "^5.0.4",
|
26 |
+
"svelte-preprocess": "^6.0.3",
|
27 |
+
"typescript": "^5.7.2",
|
28 |
+
"vite-plugin-commonjs": "^0.10.4"
|
29 |
+
},
|
30 |
+
"main_changeset": true,
|
31 |
+
"main": "./Index.svelte",
|
32 |
+
"exports": {
|
33 |
+
"./package.json": "./package.json",
|
34 |
+
".": {
|
35 |
+
"gradio": "./Index.svelte",
|
36 |
+
"svelte": "./dist/Index.svelte",
|
37 |
+
"types": "./dist/Index.svelte.d.ts"
|
38 |
+
},
|
39 |
+
"./example": {
|
40 |
+
"gradio": "./Example.svelte",
|
41 |
+
"svelte": "./dist/Example.svelte",
|
42 |
+
"types": "./dist/Example.svelte.d.ts"
|
43 |
+
},
|
44 |
+
"./base": {
|
45 |
+
"gradio": "./shared/ImagePreview.svelte",
|
46 |
+
"svelte": "./dist/shared/ImagePreview.svelte",
|
47 |
+
"types": "./dist/shared/ImagePreview.svelte.d.ts"
|
48 |
+
}
|
49 |
+
},
|
50 |
+
"peerDependencies": {
|
51 |
+
"svelte": "^4.0.0"
|
52 |
+
},
|
53 |
+
"repository": {
|
54 |
+
"type": "git",
|
55 |
+
"url": "git+https://github.com/gradio-app/gradio.git",
|
56 |
+
"directory": "js/simpleimage"
|
57 |
+
}
|
58 |
+
}
|
src/frontend/shared/AnnotationView.svelte
ADDED
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Toolbar, IconButton } from "@gradio/atoms";
|
3 |
+
import { Sketch, Square, Trash } from "@gradio/icons";
|
4 |
+
import { type AnnotatedImage, type Annotation, clamp } from "./utils";
|
5 |
+
import BoxCursor from "./BoxCursor.svelte";
|
6 |
+
import BoxPreview from "./BoxPreview.svelte";
|
7 |
+
|
8 |
+
export let value: null | AnnotatedImage = null;
|
9 |
+
export let interactive: boolean = false;
|
10 |
+
export let show_legend: boolean = true;
|
11 |
+
|
12 |
+
export let categories: string[] = [];
|
13 |
+
export let colorMap: { [key: string]: string } = {};
|
14 |
+
|
15 |
+
// Image coordinates and scale information.
|
16 |
+
type ImageRect = {
|
17 |
+
left: number;
|
18 |
+
top: number;
|
19 |
+
right: number;
|
20 |
+
bottom: number;
|
21 |
+
width: number;
|
22 |
+
height: number;
|
23 |
+
naturalWidth: number;
|
24 |
+
naturalHeight: number;
|
25 |
+
};
|
26 |
+
let imageRect: ImageRect = {
|
27 |
+
left: 0,
|
28 |
+
top: 0,
|
29 |
+
right: 1,
|
30 |
+
bottom: 1,
|
31 |
+
width: 1,
|
32 |
+
height: 1,
|
33 |
+
naturalWidth: 2,
|
34 |
+
naturalHeight: 2,
|
35 |
+
};
|
36 |
+
let imageFrame: HTMLDivElement;
|
37 |
+
let imageElement: HTMLImageElement;
|
38 |
+
|
39 |
+
// Display coordinates of the annotations.
|
40 |
+
let displayAnnotations: Annotation[] = [];
|
41 |
+
|
42 |
+
// Cursor box position in display coordinates.
|
43 |
+
let cursor: BoxCursor;
|
44 |
+
|
45 |
+
// State variables.
|
46 |
+
let selected: number | null = null;
|
47 |
+
let inserting: boolean = false;
|
48 |
+
let currentCategory: string = "";
|
49 |
+
|
50 |
+
// Attach resize observer to the image element position and size.
|
51 |
+
function onResize(node: HTMLDivElement) {
|
52 |
+
const resizeObserver = new ResizeObserver(() => {
|
53 |
+
imageRect = {
|
54 |
+
left: imageElement.offsetLeft,
|
55 |
+
top: imageElement.offsetTop,
|
56 |
+
right: imageElement.offsetLeft + imageElement.offsetWidth,
|
57 |
+
bottom: imageElement.offsetTop + imageElement.offsetHeight,
|
58 |
+
width: imageElement.offsetWidth,
|
59 |
+
height: imageElement.offsetHeight,
|
60 |
+
naturalWidth: imageElement.naturalWidth,
|
61 |
+
naturalHeight: imageElement.naturalHeight,
|
62 |
+
};
|
63 |
+
});
|
64 |
+
resizeObserver.observe(node);
|
65 |
+
return {
|
66 |
+
destroy() { resizeObserver.disconnect(); }
|
67 |
+
};
|
68 |
+
}
|
69 |
+
|
70 |
+
// Resize annotations to the display size and positions.
|
71 |
+
function updateDisplayAnnotations(value: AnnotatedImage | null, imageRect: ImageRect) {
|
72 |
+
displayAnnotations = value?.annotations.map((annotation) => {
|
73 |
+
return {
|
74 |
+
left: annotation.left / (imageRect.naturalWidth - 1) * imageRect.width + imageRect.left,
|
75 |
+
top: annotation.top / (imageRect.naturalHeight - 1) * imageRect.height + imageRect.top,
|
76 |
+
right: annotation.right / (imageRect.naturalWidth - 1) * imageRect.width + imageRect.left,
|
77 |
+
bottom: annotation.bottom / (imageRect.naturalHeight - 1) * imageRect.height + imageRect.top,
|
78 |
+
label: annotation.label,
|
79 |
+
} as Annotation;
|
80 |
+
}) || [];
|
81 |
+
|
82 |
+
if (selected !== null) {
|
83 |
+
cursor.setPosition(displayAnnotations[selected])
|
84 |
+
}
|
85 |
+
}
|
86 |
+
$: updateDisplayAnnotations(value, imageRect);
|
87 |
+
|
88 |
+
// Select an annotation box and show a cursor box.
|
89 |
+
function onSelect(event: MouseEvent, index: number) {
|
90 |
+
if (inserting) return;
|
91 |
+
selected = index;
|
92 |
+
cursor.setPosition(displayAnnotations[selected])
|
93 |
+
event.stopPropagation();
|
94 |
+
cursor.emitCursorMousedown({ clientX: event.clientX, clientY: event.clientY });
|
95 |
+
}
|
96 |
+
|
97 |
+
// Add a new annotation box if inserting, otherwise cancel the selection.
|
98 |
+
function onFrameMousedown(event: MouseEvent) {
|
99 |
+
if (!value) return;
|
100 |
+
// Cancel the selection if the click is outside the boxes.
|
101 |
+
selected = null;
|
102 |
+
if (!inserting) return;
|
103 |
+
|
104 |
+
// Add a new annotation box on insertion mode.
|
105 |
+
const rect = imageFrame.getBoundingClientRect();
|
106 |
+
const point = {
|
107 |
+
left: (event.clientX - rect.left - imageRect.left) / imageRect.width * (imageRect.naturalWidth - 1),
|
108 |
+
top: (event.clientY - rect.top - imageRect.top) / imageRect.height * (imageRect.naturalHeight - 1),
|
109 |
+
};
|
110 |
+
value.annotations.push({
|
111 |
+
left: clamp(point.left, 0, imageRect.naturalWidth - 1),
|
112 |
+
top: clamp(point.top, 0, imageRect.naturalHeight - 1),
|
113 |
+
right: clamp(point.left, 0, imageRect.naturalWidth - 1),
|
114 |
+
bottom: clamp(point.top, 0, imageRect.naturalHeight - 1),
|
115 |
+
label: currentCategory || null,
|
116 |
+
});
|
117 |
+
selected = value.annotations.length - 1;
|
118 |
+
updateDisplayAnnotations(value, imageRect);
|
119 |
+
inserting = false;
|
120 |
+
currentCategory = "";
|
121 |
+
|
122 |
+
cursor.emitAnchorMousedown("se", { clientX: event.clientX, clientY: event.clientY });
|
123 |
+
}
|
124 |
+
|
125 |
+
// Update the annotation box position when the cursor box changes.
|
126 |
+
function onCursorChange(): void {
|
127 |
+
if (value !== null && selected !== null) {
|
128 |
+
// Transform from the display coordinates to the image coordinates.
|
129 |
+
const position = cursor.getPosition();
|
130 |
+
const rect = {
|
131 |
+
left: (position.left - imageRect.left) / imageRect.width * (imageRect.naturalWidth - 1),
|
132 |
+
top: (position.top - imageRect.top) / imageRect.height * (imageRect.naturalHeight - 1),
|
133 |
+
right: (position.right - imageRect.left) / imageRect.width * (imageRect.naturalWidth - 1),
|
134 |
+
bottom: (position.bottom - imageRect.top) / imageRect.height * (imageRect.naturalHeight - 1),
|
135 |
+
}
|
136 |
+
value.annotations[selected].left = clamp(Math.round(rect.left), 0, imageRect.naturalWidth - 1);
|
137 |
+
value.annotations[selected].top = clamp(Math.round(rect.top), 0, imageRect.naturalHeight - 1);
|
138 |
+
value.annotations[selected].right = clamp(Math.round(rect.right), 0, imageRect.naturalWidth - 1);
|
139 |
+
value.annotations[selected].bottom = clamp(Math.round(rect.bottom), 0, imageRect.naturalHeight - 1);
|
140 |
+
}
|
141 |
+
}
|
142 |
+
|
143 |
+
// Remove an annotation box.
|
144 |
+
function removeAnnotation(): void {
|
145 |
+
// TODO: Invoke when the delete key is pressed.
|
146 |
+
if (value !== null && selected !== null) {
|
147 |
+
value.annotations.splice(selected, 1);
|
148 |
+
selected = null;
|
149 |
+
value = value;
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
// Switch to the inserting mode.
|
154 |
+
function onClickInsertion(label: string): void {
|
155 |
+
selected = null;
|
156 |
+
inserting = (inserting && currentCategory === label) ? false : true;
|
157 |
+
currentCategory = label;
|
158 |
+
}
|
159 |
+
|
160 |
+
// Update categories on value change.
|
161 |
+
function updateLabels(value: AnnotatedImage | null) {
|
162 |
+
if (value) {
|
163 |
+
for (let annotation of value.annotations) {
|
164 |
+
const label = annotation.label || "";
|
165 |
+
if (!categories.includes(label)) {
|
166 |
+
categories.push(label);
|
167 |
+
}
|
168 |
+
}
|
169 |
+
categories = categories;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
$: updateLabels(value);
|
173 |
+
|
174 |
+
// Update colormap on categories change.
|
175 |
+
function updateColorMap(categories: string[]) {
|
176 |
+
for (let label of categories) {
|
177 |
+
if (!colorMap[label]) {
|
178 |
+
const index = Object.keys(colorMap).length;
|
179 |
+
const hue = Math.round((index + 4) / 8 * 360) % 360; // Start from blue.
|
180 |
+
colorMap[label] = `hsl(${hue}, 100%, 50%)`;
|
181 |
+
}
|
182 |
+
}
|
183 |
+
colorMap = colorMap;
|
184 |
+
}
|
185 |
+
$: updateColorMap(categories);
|
186 |
+
</script>
|
187 |
+
|
188 |
+
{#if value !== null}
|
189 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
190 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
191 |
+
<div
|
192 |
+
bind:this={imageFrame}
|
193 |
+
class="image-frame"
|
194 |
+
use:onResize
|
195 |
+
on:mousedown|stopPropagation|preventDefault={onFrameMousedown}
|
196 |
+
>
|
197 |
+
<img
|
198 |
+
bind:this={imageElement}
|
199 |
+
class:inserting={interactive && inserting}
|
200 |
+
src={value.image.url}
|
201 |
+
alt="background"
|
202 |
+
loading="lazy"
|
203 |
+
/>
|
204 |
+
{#each displayAnnotations as annotation, index}
|
205 |
+
<BoxPreview
|
206 |
+
{annotation}
|
207 |
+
{interactive}
|
208 |
+
selectable={!inserting}
|
209 |
+
active={!interactive || selected !== index}
|
210 |
+
--box-color={colorMap[annotation.label || ""]}
|
211 |
+
--cursor={inserting ? "crosshair" : "default"}
|
212 |
+
on:mousedown={(event) => {if (interactive && !inserting) onSelect(event, index)}}
|
213 |
+
/>
|
214 |
+
{/each}
|
215 |
+
<BoxCursor
|
216 |
+
bind:this={cursor}
|
217 |
+
active={interactive && selected !== null}
|
218 |
+
frame={imageRect}
|
219 |
+
--box-color={selected !== null ? colorMap[value.annotations[selected]?.label || ""] : "white"}
|
220 |
+
on:change={onCursorChange}
|
221 |
+
/>
|
222 |
+
</div>
|
223 |
+
{#if interactive}
|
224 |
+
<Toolbar show_border={true}>
|
225 |
+
{#each categories as category}
|
226 |
+
<IconButton
|
227 |
+
Icon={(categories.length > 1) ? Square : Sketch}
|
228 |
+
show_label={categories.length > 1}
|
229 |
+
label={category}
|
230 |
+
size="medium"
|
231 |
+
padded={true}
|
232 |
+
hasPopup={true}
|
233 |
+
highlight={inserting && category === currentCategory}
|
234 |
+
color={colorMap[category] || "white"}
|
235 |
+
on:click={() => { onClickInsertion(category); }}
|
236 |
+
/>
|
237 |
+
{/each}
|
238 |
+
<IconButton
|
239 |
+
Icon={Trash}
|
240 |
+
label="Remove"
|
241 |
+
size="medium"
|
242 |
+
padded={true}
|
243 |
+
disabled={selected === null}
|
244 |
+
on:click={removeAnnotation}
|
245 |
+
/>
|
246 |
+
</Toolbar>
|
247 |
+
{:else if show_legend && categories.length > 0}
|
248 |
+
<Toolbar show_border={true}>
|
249 |
+
{#each categories as category}
|
250 |
+
<IconButton
|
251 |
+
Icon={Square}
|
252 |
+
show_label={true}
|
253 |
+
label={category}
|
254 |
+
size="medium"
|
255 |
+
padded={true}
|
256 |
+
color={colorMap[category] || "white"}
|
257 |
+
/>
|
258 |
+
{/each}
|
259 |
+
</Toolbar>
|
260 |
+
{/if}
|
261 |
+
{/if}
|
262 |
+
|
263 |
+
<style>
|
264 |
+
.image-frame {
|
265 |
+
position: relative;
|
266 |
+
width: 100%;
|
267 |
+
height: 100%;
|
268 |
+
padding: 10px;
|
269 |
+
}
|
270 |
+
.image-frame :global(img) {
|
271 |
+
width: var(--size-full);
|
272 |
+
height: var(--size-full);
|
273 |
+
object-fit: contain;
|
274 |
+
user-select: none;
|
275 |
+
-moz-user-select: none;
|
276 |
+
-webkit-user-drag: none;
|
277 |
+
}
|
278 |
+
.inserting {
|
279 |
+
cursor: crosshair;
|
280 |
+
}
|
281 |
+
</style>
|
src/frontend/shared/BoxCursor.svelte
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { createEventDispatcher } from "svelte";
|
3 |
+
import { type Box, clamp } from "./utils";
|
4 |
+
|
5 |
+
export let frame: Box;
|
6 |
+
export let dragging: boolean = false;
|
7 |
+
export let active: boolean = false;
|
8 |
+
|
9 |
+
type AnchorLocation = "nw" | "w" | "sw" | "s" | "se" | "e" | "ne" | "n";
|
10 |
+
|
11 |
+
let left: number = 0;
|
12 |
+
let top: number = 0;
|
13 |
+
let right: number = 0;
|
14 |
+
let bottom: number = 0;
|
15 |
+
|
16 |
+
export function setPosition(box: Box): void {
|
17 |
+
left = box.left;
|
18 |
+
top = box.top;
|
19 |
+
right = box.right;
|
20 |
+
bottom = box.bottom;
|
21 |
+
}
|
22 |
+
|
23 |
+
export function getPosition(): Box {
|
24 |
+
return { left, top, right, bottom };
|
25 |
+
}
|
26 |
+
|
27 |
+
let cursorBody: HTMLDivElement | null = null;
|
28 |
+
let anchor: { [key in AnchorLocation]: HTMLDivElement | null } = {
|
29 |
+
nw: null,
|
30 |
+
n: null,
|
31 |
+
ne: null,
|
32 |
+
w: null,
|
33 |
+
sw: null,
|
34 |
+
s: null,
|
35 |
+
se: null,
|
36 |
+
e: null,
|
37 |
+
};
|
38 |
+
|
39 |
+
const dispatch = createEventDispatcher<{
|
40 |
+
change?: Box;
|
41 |
+
drag?: never;
|
42 |
+
}>();
|
43 |
+
|
44 |
+
$: if (dragging) dispatch("drag");
|
45 |
+
|
46 |
+
// Drag to move the cursor.
|
47 |
+
function onCursorMousedown(event: MouseEvent): void {
|
48 |
+
const startX = event.clientX;
|
49 |
+
const startY = event.clientY;
|
50 |
+
const offset: Box = { left, top, right, bottom };
|
51 |
+
const width = right - left;
|
52 |
+
const height = bottom - top;
|
53 |
+
|
54 |
+
dragging = true;
|
55 |
+
|
56 |
+
function onCursorMousemove(event: MouseEvent): void {
|
57 |
+
const dx = clamp(event.clientX - startX, frame.left - offset.left, frame.right - offset.right);
|
58 |
+
const dy = clamp(event.clientY - startY, frame.top - offset.top, frame.bottom - offset.bottom);
|
59 |
+
left = offset.left + dx;
|
60 |
+
top = offset.top + dy;
|
61 |
+
right = offset.right + dx;
|
62 |
+
bottom = offset.bottom + dy;
|
63 |
+
dispatch("change", { left, top, right, bottom });
|
64 |
+
event.preventDefault();
|
65 |
+
event.stopPropagation();
|
66 |
+
}
|
67 |
+
function onCursorMouseup(event: MouseEvent): void {
|
68 |
+
// TODO: Case when the cursor is outside the window.
|
69 |
+
window.removeEventListener("mousemove", onCursorMousemove);
|
70 |
+
window.removeEventListener("mouseup", onCursorMouseup);
|
71 |
+
event.preventDefault();
|
72 |
+
event.stopPropagation();
|
73 |
+
dragging = false;
|
74 |
+
}
|
75 |
+
|
76 |
+
window.addEventListener("mousemove", onCursorMousemove);
|
77 |
+
window.addEventListener("mouseup", onCursorMouseup);
|
78 |
+
}
|
79 |
+
|
80 |
+
// Drag to resize the cursor.
|
81 |
+
function onAnchorMousedown(event: MouseEvent, location: string): void {
|
82 |
+
const startX = event.clientX;
|
83 |
+
const startY = event.clientY;
|
84 |
+
const offset = { left, top, right, bottom };
|
85 |
+
|
86 |
+
dragging = true;
|
87 |
+
|
88 |
+
function onAnchorMousemove(event: MouseEvent): void {
|
89 |
+
const dx = event.clientX - startX;
|
90 |
+
const dy = event.clientY - startY;
|
91 |
+
|
92 |
+
if (location.includes("w")) {
|
93 |
+
if (offset.left + dx <= offset.right) {
|
94 |
+
left = clamp(offset.left + dx, frame.left, frame.right);
|
95 |
+
}
|
96 |
+
else {
|
97 |
+
left = offset.right;
|
98 |
+
right = clamp(offset.left + dx, frame.left, frame.right);
|
99 |
+
}
|
100 |
+
}
|
101 |
+
else if (location.includes("e")) {
|
102 |
+
if (offset.right + dx >= offset.left) {
|
103 |
+
right = clamp(offset.right + dx, frame.left, frame.right);
|
104 |
+
}
|
105 |
+
else {
|
106 |
+
right = offset.left;
|
107 |
+
left = clamp(offset.right + dx, frame.left, frame.right);
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
if (location.includes("n")) {
|
112 |
+
if (offset.top + dy <= offset.bottom) {
|
113 |
+
top = clamp(offset.top + dy, frame.top, frame.bottom);
|
114 |
+
}
|
115 |
+
else {
|
116 |
+
top = offset.bottom;
|
117 |
+
bottom = clamp(offset.top + dy, frame.top, frame.bottom);
|
118 |
+
}
|
119 |
+
}
|
120 |
+
else if (location.includes("s")) {
|
121 |
+
if (offset.bottom + dy >= offset.top) {
|
122 |
+
bottom = clamp(offset.bottom + dy, frame.top, frame.bottom);
|
123 |
+
}
|
124 |
+
else {
|
125 |
+
bottom = offset.bottom;
|
126 |
+
top = clamp(offset.bottom + dy, frame.top, frame.bottom);
|
127 |
+
}
|
128 |
+
}
|
129 |
+
dispatch("change", { left, top, right, bottom });
|
130 |
+
event.preventDefault();
|
131 |
+
event.stopPropagation();
|
132 |
+
}
|
133 |
+
function onAnchorMouseup(event: MouseEvent): void {
|
134 |
+
window.removeEventListener("mousemove", onAnchorMousemove);
|
135 |
+
window.removeEventListener("mouseup", onAnchorMouseup);
|
136 |
+
event.preventDefault();
|
137 |
+
event.stopPropagation();
|
138 |
+
dragging = false;
|
139 |
+
}
|
140 |
+
|
141 |
+
window.addEventListener("mousemove", onAnchorMousemove);
|
142 |
+
window.addEventListener("mouseup", onAnchorMouseup);
|
143 |
+
}
|
144 |
+
|
145 |
+
export function emitCursorMousedown(options: any = null): void {
|
146 |
+
cursorBody?.dispatchEvent(new MouseEvent("mousedown", options));
|
147 |
+
}
|
148 |
+
|
149 |
+
export function emitAnchorMousedown(location: AnchorLocation, options: any = null): void {
|
150 |
+
anchor[location]?.dispatchEvent(new MouseEvent("mousedown", options));
|
151 |
+
}
|
152 |
+
</script>
|
153 |
+
|
154 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
155 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
156 |
+
<div
|
157 |
+
bind:this={cursorBody}
|
158 |
+
class="box-cursor"
|
159 |
+
class:inactive={!active}
|
160 |
+
class:selectable={active && !dragging}
|
161 |
+
style:left={left + "px"}
|
162 |
+
style:top={top + "px"}
|
163 |
+
style:width={(right - left) + "px"}
|
164 |
+
style:height={(bottom - top) + "px"}
|
165 |
+
on:mousedown|stopPropagation|preventDefault={onCursorMousedown}
|
166 |
+
on:click|stopPropagation|preventDefault
|
167 |
+
>
|
168 |
+
</div>
|
169 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
170 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
171 |
+
<div
|
172 |
+
bind:this={anchor.nw}
|
173 |
+
class="box-anchor"
|
174 |
+
class:inactive={!active}
|
175 |
+
style:cursor="nwse-resize"
|
176 |
+
style:left={(left - 5) + "px"}
|
177 |
+
style:top={(top - 5) + "px"}
|
178 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "nw")}
|
179 |
+
on:click|stopPropagation|preventDefault
|
180 |
+
>
|
181 |
+
</div>
|
182 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
183 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
184 |
+
<div
|
185 |
+
bind:this={anchor.w}
|
186 |
+
class="box-anchor"
|
187 |
+
class:inactive={!active}
|
188 |
+
style:cursor="ew-resize"
|
189 |
+
style:left={(left - 5) + "px"}
|
190 |
+
style:top={((top + bottom) / 2 - 5) + "px"}
|
191 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "w")}
|
192 |
+
on:click|stopPropagation|preventDefault
|
193 |
+
>
|
194 |
+
</div>
|
195 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
196 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
197 |
+
<div
|
198 |
+
bind:this={anchor.sw}
|
199 |
+
class="box-anchor"
|
200 |
+
class:inactive={!active}
|
201 |
+
style:cursor="nesw-resize"
|
202 |
+
style:left={(left - 5) + "px"}
|
203 |
+
style:top={(bottom - 5) + "px"}
|
204 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "sw")}
|
205 |
+
on:click|stopPropagation|preventDefault
|
206 |
+
>
|
207 |
+
</div>
|
208 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
209 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
210 |
+
<div
|
211 |
+
bind:this={anchor.s}
|
212 |
+
class="box-anchor"
|
213 |
+
class:inactive={!active}
|
214 |
+
style:cursor="ns-resize"
|
215 |
+
style:left={((left + right) / 2 - 5) + "px"}
|
216 |
+
style:top={(bottom - 5) + "px"}
|
217 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "s")}
|
218 |
+
on:click|stopPropagation|preventDefault
|
219 |
+
>
|
220 |
+
</div>
|
221 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
222 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
223 |
+
<div
|
224 |
+
bind:this={anchor.se}
|
225 |
+
class="box-anchor"
|
226 |
+
class:inactive={!active}
|
227 |
+
style:cursor="nwse-resize"
|
228 |
+
style:left={(right - 5) + "px"}
|
229 |
+
style:top={(bottom - 5) + "px"}
|
230 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "se")}
|
231 |
+
on:click|stopPropagation|preventDefault
|
232 |
+
>
|
233 |
+
</div>
|
234 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
235 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
236 |
+
<div
|
237 |
+
bind:this={anchor.e}
|
238 |
+
class="box-anchor"
|
239 |
+
class:inactive={!active}
|
240 |
+
style:cursor="ew-resize"
|
241 |
+
style:left={(right - 5) + "px"}
|
242 |
+
style:top={((top + bottom) / 2 - 5) + "px"}
|
243 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "e")}
|
244 |
+
on:click|stopPropagation|preventDefault
|
245 |
+
></div>
|
246 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
247 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
248 |
+
<div
|
249 |
+
bind:this={anchor.ne}
|
250 |
+
class="box-anchor"
|
251 |
+
class:inactive={!active}
|
252 |
+
style:cursor="nesw-resize"
|
253 |
+
style:left={(right - 5) + "px"}
|
254 |
+
style:top={(top - 5) + "px"}
|
255 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "ne")}
|
256 |
+
on:click|stopPropagation|preventDefault
|
257 |
+
>
|
258 |
+
</div>
|
259 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
260 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
261 |
+
<div
|
262 |
+
bind:this={anchor.n}
|
263 |
+
class="box-anchor"
|
264 |
+
class:inactive={!active}
|
265 |
+
style:cursor="ns-resize"
|
266 |
+
style:left={((left + right) / 2 - 5) + "px"}
|
267 |
+
style:top={(top - 5) + "px"}
|
268 |
+
on:mousedown|stopPropagation|preventDefault={(event) => onAnchorMousedown(event, "n")}
|
269 |
+
on:click|stopPropagation|preventDefault
|
270 |
+
></div>
|
271 |
+
|
272 |
+
<style>
|
273 |
+
.box-cursor {
|
274 |
+
position: absolute;
|
275 |
+
border-width: 1px;
|
276 |
+
border-style: solid;
|
277 |
+
border-color: var(--box-color, white);
|
278 |
+
cursor: move;
|
279 |
+
}
|
280 |
+
.box-cursor:hover {
|
281 |
+
background-color: color-mix(in hsl, var(--box-color) 10%, transparent);
|
282 |
+
}
|
283 |
+
.inactive {
|
284 |
+
display: none;
|
285 |
+
}
|
286 |
+
.box-anchor {
|
287 |
+
position: absolute;
|
288 |
+
border: 1px solid white;
|
289 |
+
background-color: var(--box-color, white);
|
290 |
+
width: 10px;
|
291 |
+
height: 10px;
|
292 |
+
}
|
293 |
+
</style>
|
src/frontend/shared/BoxPreview.svelte
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import {type Annotation} from "./utils";
|
3 |
+
|
4 |
+
export let annotation: Annotation;
|
5 |
+
export let interactive: boolean;
|
6 |
+
export let selectable: boolean;
|
7 |
+
export let active: boolean = true;
|
8 |
+
</script>
|
9 |
+
|
10 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
11 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
12 |
+
<div
|
13 |
+
class="box-preview"
|
14 |
+
class:selectable={interactive && selectable}
|
15 |
+
class:inactive={!active}
|
16 |
+
style:left={annotation.left + "px"}
|
17 |
+
style:top={annotation.top + "px"}
|
18 |
+
style:width={(annotation.right - annotation.left) + "px"}
|
19 |
+
style:height={(annotation.bottom - annotation.top) + "px"}
|
20 |
+
data-label={annotation.label}
|
21 |
+
on:mousedown
|
22 |
+
>
|
23 |
+
</div>
|
24 |
+
|
25 |
+
<style>
|
26 |
+
.box-preview {
|
27 |
+
position: absolute;
|
28 |
+
border-width: 1px;
|
29 |
+
border-style: solid;
|
30 |
+
border-color: var(--box-color, white);
|
31 |
+
cursor: var(--cursor, default);
|
32 |
+
}
|
33 |
+
.selectable {
|
34 |
+
cursor: pointer;
|
35 |
+
}
|
36 |
+
.selectable:hover {
|
37 |
+
background-color: color-mix(in hsl, var(--box-color) 10%, transparent);
|
38 |
+
}
|
39 |
+
.inactive {
|
40 |
+
display: none;
|
41 |
+
}
|
42 |
+
</style>
|
src/frontend/shared/ImagePreview.svelte
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import {
|
3 |
+
BlockLabel,
|
4 |
+
Empty,
|
5 |
+
IconButton,
|
6 |
+
IconButtonWrapper
|
7 |
+
} from "@gradio/atoms";
|
8 |
+
import { Download } from "@gradio/icons";
|
9 |
+
import { DownloadLink } from "@gradio/wasm/svelte";
|
10 |
+
|
11 |
+
import { Image as ImageIcon } from "@gradio/icons";
|
12 |
+
import type { I18nFormatter } from "@gradio/utils";
|
13 |
+
import AnnotationView from "./AnnotationView.svelte";
|
14 |
+
import { type AnnotatedImage, encodeToDataURL } from "./utils";
|
15 |
+
|
16 |
+
export let value: null | AnnotatedImage = null;
|
17 |
+
export let label: string | undefined = undefined;
|
18 |
+
export let show_label: boolean;
|
19 |
+
export let show_download_button = true;
|
20 |
+
export let i18n: I18nFormatter;
|
21 |
+
export let categories: string[] = [];
|
22 |
+
</script>
|
23 |
+
|
24 |
+
<BlockLabel
|
25 |
+
{show_label}
|
26 |
+
Icon={ImageIcon}
|
27 |
+
label={label || i18n("image.image")}
|
28 |
+
/>
|
29 |
+
{#if value === null || !value.image.url}
|
30 |
+
<Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
|
31 |
+
{:else}
|
32 |
+
<IconButtonWrapper>
|
33 |
+
{#if show_download_button}
|
34 |
+
<DownloadLink href={encodeToDataURL(value)} download={(value.image.orig_name || "image") + ".json"}>
|
35 |
+
<IconButton Icon={Download} label={i18n("common.download")} />
|
36 |
+
</DownloadLink>
|
37 |
+
{/if}
|
38 |
+
</IconButtonWrapper>
|
39 |
+
<AnnotationView {value} {categories}/>
|
40 |
+
{/if}
|
src/frontend/shared/ImageUploader.svelte
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { createEventDispatcher, tick } from "svelte";
|
3 |
+
import { BlockLabel, IconButton, IconButtonWrapper } from "@gradio/atoms";
|
4 |
+
import { Image as ImageIcon, Clear as ClearIcon } from "@gradio/icons";
|
5 |
+
|
6 |
+
import { Upload } from "@gradio/upload";
|
7 |
+
import type { FileData, Client } from "@gradio/client";
|
8 |
+
import AnnotationView from "./AnnotationView.svelte";
|
9 |
+
import type { AnnotatedImage } from "./utils";
|
10 |
+
|
11 |
+
export let value: null | AnnotatedImage = null;
|
12 |
+
export let label: string | undefined = undefined;
|
13 |
+
export let show_label: boolean;
|
14 |
+
export let root: string;
|
15 |
+
export let upload: Client["upload"];
|
16 |
+
export let stream_handler: Client["stream"];
|
17 |
+
export let categories: string[] = [];
|
18 |
+
|
19 |
+
let upload_component: Upload;
|
20 |
+
let uploading = false;
|
21 |
+
|
22 |
+
function handle_upload({ detail }: CustomEvent<FileData>): void {
|
23 |
+
// TODO: Support annotation upload via JSON.
|
24 |
+
value = { image: detail, annotations: [] } as AnnotatedImage;
|
25 |
+
dispatch("upload");
|
26 |
+
}
|
27 |
+
$: if (uploading) value = null;
|
28 |
+
|
29 |
+
const dispatch = createEventDispatcher<{
|
30 |
+
change?: never;
|
31 |
+
clear?: never;
|
32 |
+
drag: boolean;
|
33 |
+
upload?: never;
|
34 |
+
}>();
|
35 |
+
|
36 |
+
let dragging = false;
|
37 |
+
$: dispatch("drag", dragging);
|
38 |
+
</script>
|
39 |
+
|
40 |
+
<BlockLabel {show_label} Icon={ImageIcon} label={label || "Image"} />
|
41 |
+
|
42 |
+
<div data-testid="image" class="image-container">
|
43 |
+
{#if value?.image.url}
|
44 |
+
<IconButtonWrapper>
|
45 |
+
<IconButton
|
46 |
+
Icon={ClearIcon}
|
47 |
+
label="Remove Image"
|
48 |
+
on:click={(event) => {
|
49 |
+
value = null;
|
50 |
+
dispatch("clear");
|
51 |
+
event.stopPropagation();
|
52 |
+
}}
|
53 |
+
/>
|
54 |
+
</IconButtonWrapper>
|
55 |
+
{/if}
|
56 |
+
<div class="upload-container">
|
57 |
+
<Upload
|
58 |
+
{upload}
|
59 |
+
{stream_handler}
|
60 |
+
hidden={value !== null}
|
61 |
+
bind:this={upload_component}
|
62 |
+
bind:uploading
|
63 |
+
bind:dragging
|
64 |
+
filetype="image/*"
|
65 |
+
on:load={handle_upload}
|
66 |
+
on:error
|
67 |
+
{root}
|
68 |
+
>
|
69 |
+
{#if value === null}
|
70 |
+
<slot />
|
71 |
+
{/if}
|
72 |
+
</Upload>
|
73 |
+
{#if value !== null}
|
74 |
+
<AnnotationView bind:value {categories} interactive={true} />
|
75 |
+
{/if}
|
76 |
+
</div>
|
77 |
+
</div>
|
78 |
+
|
79 |
+
<style>
|
80 |
+
.upload-container {
|
81 |
+
height: 100%;
|
82 |
+
width: 100%;
|
83 |
+
flex-shrink: 1;
|
84 |
+
max-height: 100%;
|
85 |
+
}
|
86 |
+
.image-container {
|
87 |
+
display: flex;
|
88 |
+
height: 100%;
|
89 |
+
flex-direction: column;
|
90 |
+
justify-content: center;
|
91 |
+
align-items: center;
|
92 |
+
max-height: 100%;
|
93 |
+
}
|
94 |
+
</style>
|
src/frontend/shared/utils.ts
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { FileData } from "@gradio/client";
|
2 |
+
|
3 |
+
export interface Box {
|
4 |
+
left: number,
|
5 |
+
top: number,
|
6 |
+
right: number,
|
7 |
+
bottom: number,
|
8 |
+
};
|
9 |
+
|
10 |
+
export interface Annotation extends Box {
|
11 |
+
label: string | null,
|
12 |
+
};
|
13 |
+
|
14 |
+
export type AnnotatedImage = {
|
15 |
+
image: FileData,
|
16 |
+
annotations: Annotation[]
|
17 |
+
};
|
18 |
+
|
19 |
+
export function clamp(value: number, min: number, max: number): number {
|
20 |
+
return Math.min(Math.max(value, min), max);
|
21 |
+
}
|
22 |
+
|
23 |
+
export function encodeToDataURL(data: any, mimeType: string = "application/json", charset: string = "utf-8"): string {
|
24 |
+
return "data:" + mimeType + ";charset=" + charset + "," + encodeURIComponent(JSON.stringify(data));
|
25 |
+
}
|
src/frontend/tsconfig.json
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": "@tsconfig/svelte/tsconfig.json"
|
3 |
+
}
|
src/pyproject.toml
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[build-system]
|
2 |
+
requires = [
|
3 |
+
"hatchling",
|
4 |
+
"hatch-requirements-txt",
|
5 |
+
"hatch-fancy-pypi-readme>=22.5.0",
|
6 |
+
]
|
7 |
+
build-backend = "hatchling.build"
|
8 |
+
|
9 |
+
[project]
|
10 |
+
name = "gradio_bbox_annotator"
|
11 |
+
version = "0.1.0"
|
12 |
+
description = "Bounding box annotation tool"
|
13 |
+
readme = "README.md"
|
14 |
+
license = "apache-2.0"
|
15 |
+
requires-python = ">=3.10"
|
16 |
+
authors = [{ name = "Kota Yamaguchi", email = "[email protected]" }]
|
17 |
+
keywords = ["gradio-custom-component", "image", "annotation", "bbox"]
|
18 |
+
# Add dependencies here
|
19 |
+
dependencies = ["gradio>=4.0,<6.0"]
|
20 |
+
classifiers = [
|
21 |
+
'Development Status :: 3 - Alpha',
|
22 |
+
'Operating System :: OS Independent',
|
23 |
+
'Programming Language :: Python :: 3',
|
24 |
+
'Programming Language :: Python :: 3 :: Only',
|
25 |
+
'Programming Language :: Python :: 3.9',
|
26 |
+
'Programming Language :: Python :: 3.10',
|
27 |
+
'Programming Language :: Python :: 3.11',
|
28 |
+
'Programming Language :: Python :: 3.12',
|
29 |
+
'Programming Language :: Python :: 3.13',
|
30 |
+
'Topic :: Scientific/Engineering',
|
31 |
+
'Topic :: Scientific/Engineering :: Artificial Intelligence',
|
32 |
+
'Topic :: Scientific/Engineering :: Visualization',
|
33 |
+
]
|
34 |
+
|
35 |
+
[project.urls]
|
36 |
+
repository = "https://github.com/kyamagu/gradio-bbox-annotator"
|
37 |
+
# space = "your space url"
|
38 |
+
|
39 |
+
[project.optional-dependencies]
|
40 |
+
dev = ["build", "twine"]
|
41 |
+
|
42 |
+
[tool.hatch.build]
|
43 |
+
artifacts = ["/backend/gradio_bbox_annotator/templates", "*.pyi"]
|
44 |
+
|
45 |
+
[tool.hatch.build.targets.wheel]
|
46 |
+
packages = ["/backend/gradio_bbox_annotator"]
|