Skip to content

Commit b292b98

Browse files
author
GradientSurfer
committed
v0.0.1
1 parent d35d38a commit b292b98

39 files changed

+6765
-0
lines changed

.gitignore

+135
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.vscode
2+
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]
@@ -158,3 +160,136 @@ cython_debug/
158160
# and can be added to the global gitignore or merged into this file. For a more nuclear
159161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160162
#.idea/
163+
164+
# ------------------- Node
165+
166+
# Logs
167+
logs
168+
*.log
169+
npm-debug.log*
170+
yarn-debug.log*
171+
yarn-error.log*
172+
lerna-debug.log*
173+
.pnpm-debug.log*
174+
175+
# Diagnostic reports (https://nodejs.org/api/report.html)
176+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
177+
178+
# Runtime data
179+
pids
180+
*.pid
181+
*.seed
182+
*.pid.lock
183+
184+
# Directory for instrumented libs generated by jscoverage/JSCover
185+
lib-cov
186+
187+
# Coverage directory used by tools like istanbul
188+
coverage
189+
*.lcov
190+
191+
# nyc test coverage
192+
.nyc_output
193+
194+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
195+
.grunt
196+
197+
# Bower dependency directory (https://bower.io/)
198+
bower_components
199+
200+
# node-waf configuration
201+
.lock-wscript
202+
203+
# Compiled binary addons (https://nodejs.org/api/addons.html)
204+
build/Release
205+
206+
# Dependency directories
207+
node_modules/
208+
jspm_packages/
209+
210+
# Snowpack dependency directory (https://snowpack.dev/)
211+
web_modules/
212+
213+
# TypeScript cache
214+
*.tsbuildinfo
215+
216+
# Optional npm cache directory
217+
.npm
218+
219+
# Optional eslint cache
220+
.eslintcache
221+
222+
# Optional stylelint cache
223+
.stylelintcache
224+
225+
# Microbundle cache
226+
.rpt2_cache/
227+
.rts2_cache_cjs/
228+
.rts2_cache_es/
229+
.rts2_cache_umd/
230+
231+
# Optional REPL history
232+
.node_repl_history
233+
234+
# Output of 'npm pack'
235+
*.tgz
236+
237+
# Yarn Integrity file
238+
.yarn-integrity
239+
240+
# dotenv environment variable files
241+
.env
242+
.env.development.local
243+
.env.test.local
244+
.env.production.local
245+
.env.local
246+
247+
# parcel-bundler cache (https://parceljs.org/)
248+
.cache
249+
.parcel-cache
250+
251+
# Next.js build output
252+
.next
253+
out
254+
255+
# Nuxt.js build / generate output
256+
.nuxt
257+
dist
258+
259+
# Gatsby files
260+
.cache/
261+
# Comment in the public line in if your project uses Gatsby and not Next.js
262+
# https://nextjs.org/blog/next-9-1#public-directory-support
263+
# public
264+
265+
# vuepress build output
266+
.vuepress/dist
267+
268+
# vuepress v2.x temp and cache directory
269+
.temp
270+
.cache
271+
272+
# Docusaurus cache and generated files
273+
.docusaurus
274+
275+
# Serverless directories
276+
.serverless/
277+
278+
# FuseBox cache
279+
.fusebox/
280+
281+
# DynamoDB Local files
282+
.dynamodb/
283+
284+
# TernJS port file
285+
.tern-port
286+
287+
# Stores VSCode versions used for testing VSCode extensions
288+
.vscode-test
289+
290+
# yarn v2
291+
.yarn/cache
292+
.yarn/unplugged
293+
.yarn/build-state.yml
294+
.yarn/install-state.gz
295+
.pnp.*

Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.11-slim-bookworm
2+
3+
WORKDIR /app
4+
COPY LICENSE LICENSE
5+
COPY README.md README.md
6+
COPY pyproject.toml pyproject.toml
7+
COPY ./draw2img ./draw2img
8+
RUN --mount=type=cache,target=/root/.cache/pip pip install .
9+
ENV HF_HOME=/root/.cache/huggingface
10+
CMD ["python3", "draw2img/main.py", "--host", "0.0.0.0"]

README.md

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Draw2Img
2+
3+
A simple web UI for interactive *text-guided image to image generation*, intended for any age and level of expertise.
4+
5+
# Features
6+
7+
- web based UI, interactive canvas with basic paint tool & color picker
8+
- real-time text-guided image to image generation via [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo) (512 x 512 pixels)
9+
- editable prompt, seed, steps, and strength parameters
10+
- export button to save input and output images as PNG files, along with the parameters as JSON
11+
- multi-threaded server supports multiple concurrent users
12+
- easy to host on your LAN for creative fun with family and friends
13+
- local (no internet required), private, & open source
14+
15+
# Requirements
16+
17+
Hardware:
18+
- **GPU** with at least 10 GB VRAM is recommended, but not strictly required
19+
- CPU only environments are supported but image generation will be significantly slower
20+
21+
Operating System:
22+
- Linux (tested)
23+
- Windows & Mac (should work in theory)
24+
25+
Software:
26+
- Python >= 3.7
27+
28+
Browser:
29+
- any modern browser (Firefox, Chrome, Edge, Safari, etc)
30+
31+
Internet:
32+
- not required (except to download the model once on first run)
33+
34+
# Usage
35+
36+
## Install
37+
38+
Clone this repository
39+
40+
```bash
41+
git clone https://github.com/GradientSurfer/Draw2Img.git
42+
```
43+
44+
Install the dependencies
45+
46+
```bash
47+
pip install .
48+
```
49+
50+
## Start Server
51+
52+
Start the server, by default it will listen on [http://localhost:8080](http://localhost:8080)
53+
54+
```bash
55+
python draw2img/main.py
56+
```
57+
58+
Navigate to the HTTP URL via your browser, and...that's it, have fun!
59+
60+
### Options
61+
62+
You can host the server on a specific interface and port via the `--host` and `--port` options. For example to listen on `192.168.1.123:4269`:
63+
64+
```bash
65+
python draw2img/main.py --host 192.168.1.123 --port 4269
66+
```
67+
68+
To see all available options
69+
70+
```bash
71+
python draw2img/main.py --help
72+
```
73+
74+
### Container (Docker/Podman)
75+
76+
You can use the provided Dockerfile to build and run a container image:
77+
78+
```bash
79+
DOCKER_BUILDKIT=1 docker build -t draw2img .
80+
```
81+
82+
Be sure to mount your huggingface cache directory to avoid downloading the SDXL-Turbo model every time the container starts (`-v ~/.cache/huggingface:/root/.cache/huggingface`). To use GPU(s) you'll need the `--gpus all` option.
83+
84+
```bash
85+
docker run -it -p 8080:8080 -p 8079:8079 -v ~/.cache/huggingface:/root/.cache/huggingface --gpus all draw2img
86+
```
87+
88+
# Development
89+
90+
## Server
91+
92+
Install the Python package in editable mode
93+
94+
```bash
95+
pip install -e .
96+
```
97+
98+
## UI
99+
100+
The UI can be built manually (static files are output to `dist` folder)
101+
102+
```bash
103+
cd draw2img/ui
104+
npm run build
105+
```
106+
107+
Or alternatively, the Vue 3 template comes with a file server & hot reloading for easy development
108+
109+
```bash
110+
npm run dev
111+
```
112+
113+
### Container (Docker/Podman)
114+
115+
You can avoid installing `node` and `npm` on your host machine by using a container image with the UI development toolchain (`node:lts-slim`).
116+
117+
```bash
118+
cd draw2img/ui
119+
# build the UI
120+
docker run -it -v $(pwd):/ui -p 5173:5173 node:lts-slim bash -c "cd ui && npm run build"
121+
# or run the dev server
122+
docker run -it -v $(pwd):/ui -p 5173:5173 node:lts-slim bash -c "cd ui && npm run dev -- --host"
123+
```
124+
125+
## Design Notes
126+
127+
The backend is a multi-threaded Python websocket server, that also serves the static files for the web UI.
128+
129+
The front-end is a JS/TS application (Vue 3) bootstrapped via `npm create vue@latest`. The build produces static files that can be served with any web server software.
130+
131+
### Performance
132+
133+
Although the websocket server is multi-threaded, a mutex protects the singleton `Pipeline` object because it is not thread safe. This means image generation is effectively single threaded, so performance scales poorly as the number of concurrent users increases, and CPU/GPU resources may be underutilized. Additionally, there is no batching of requests for inference, mainly due to the lack of underlying support for varying certain parameters (such as strength and steps) across samples within a single batch.
134+
135+
In practice the multithreading/lock primitives exhibit a degree of fairness, so limited CPU/GPU resources appear to be shared relatively evenly among concurrent users, even as incoming requests queue up. Technically though, Python doesn't make any guarantees regarding the order of thread scheduling when a lock is contended (according to the docs).
136+
137+
If you need additional concurrency and have available RAM/VRAM + compute, consider starting multiple instances of the `draw2img` process.
138+
139+
### Security
140+
141+
This code has not been audited for vulnerabilities.
142+
143+
# Contributions
144+
145+
Contributions are welcome! Please keep in mind the ethos of this project when opening PRs or issues.
146+
147+
# Safety
148+
149+
There is no safety filter to prevent offensive or undesireable images from being generated, please use discretion. Supervise children as usual with any computer/internet use.
150+
151+
# Non-goals / Other Projects
152+
153+
If you're an advanced user looking for more functionality, other projects like [Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) or [ComfyUI](https://github.com/comfyanonymous/ComfyUI) may fit your needs better.
154+
155+
# License
156+
157+
[MIT](LICENSE)
158+
159+
See the [Stability AI Non-Commercial License for SDXL-Turbo](https://github.com/Stability-AI/generative-models/blob/main/model_licenses/LICENSE-SDXL-Turbo) and their [acceptable use policy](https://stability.ai/use-policy).

draw2img/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .main import main

draw2img/main.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import http.server
2+
import logging
3+
import os
4+
import sys
5+
import threading
6+
from http.server import ThreadingHTTPServer
7+
from threading import Thread
8+
9+
import fire
10+
11+
from draw2img.server import server
12+
13+
logger = logging.getLogger("draw2img")
14+
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
15+
logging.getLogger("websockets.server").setLevel(logging.WARNING)
16+
logging.getLogger("http.server").setLevel(logging.WARNING)
17+
# path to static files for serving web UI
18+
UI_DIR = os.path.join(os.path.dirname(__file__), "ui/dist")
19+
20+
21+
def main(host: str = "localhost", port: int = 8080, dir: str = UI_DIR):
22+
"""Starts a draw2img process, blocks until keyboard interrupt."""
23+
24+
try:
25+
# start web socket server in a separate thread
26+
wss_port = port - 1
27+
stop_event: threading.Event = threading.Event()
28+
thread = Thread(target=server, args=(host, wss_port, stop_event))
29+
thread.start()
30+
31+
# start static file server for web UI
32+
class Handler(http.server.SimpleHTTPRequestHandler):
33+
def __init__(self, *args, **kwargs):
34+
super().__init__(*args, directory=dir, **kwargs)
35+
36+
with ThreadingHTTPServer((host, port), Handler) as httpd:
37+
logger.info(f"Web UI URL: http://{host}:{port}")
38+
httpd.serve_forever()
39+
40+
except KeyboardInterrupt:
41+
stop_event.set()
42+
except Exception:
43+
logger.exception("draw2img unexpected error")
44+
finally:
45+
thread.join()
46+
47+
return
48+
49+
50+
if __name__ == "__main__":
51+
fire.Fire(main)

draw2img/server/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .main import server

0 commit comments

Comments
 (0)