Skip to content

Commit

Permalink
v0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
GradientSurfer committed Jan 4, 2024
1 parent d35d38a commit b292b98
Show file tree
Hide file tree
Showing 39 changed files with 6,765 additions and 0 deletions.
135 changes: 135 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -158,3 +160,136 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# ------------------- Node

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.11-slim-bookworm

WORKDIR /app
COPY LICENSE LICENSE
COPY README.md README.md
COPY pyproject.toml pyproject.toml
COPY ./draw2img ./draw2img
RUN --mount=type=cache,target=/root/.cache/pip pip install .
ENV HF_HOME=/root/.cache/huggingface
CMD ["python3", "draw2img/main.py", "--host", "0.0.0.0"]
159 changes: 159 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Draw2Img

A simple web UI for interactive *text-guided image to image generation*, intended for any age and level of expertise.

# Features

- web based UI, interactive canvas with basic paint tool & color picker
- real-time text-guided image to image generation via [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo) (512 x 512 pixels)
- editable prompt, seed, steps, and strength parameters
- export button to save input and output images as PNG files, along with the parameters as JSON
- multi-threaded server supports multiple concurrent users
- easy to host on your LAN for creative fun with family and friends
- local (no internet required), private, & open source

# Requirements

Hardware:
- **GPU** with at least 10 GB VRAM is recommended, but not strictly required
- CPU only environments are supported but image generation will be significantly slower

Operating System:
- Linux (tested)
- Windows & Mac (should work in theory)

Software:
- Python >= 3.7

Browser:
- any modern browser (Firefox, Chrome, Edge, Safari, etc)

Internet:
- not required (except to download the model once on first run)

# Usage

## Install

Clone this repository

```bash
git clone https://github.com/GradientSurfer/Draw2Img.git
```

Install the dependencies

```bash
pip install .
```

## Start Server

Start the server, by default it will listen on [http://localhost:8080](http://localhost:8080)

```bash
python draw2img/main.py
```

Navigate to the HTTP URL via your browser, and...that's it, have fun!

### Options

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`:

```bash
python draw2img/main.py --host 192.168.1.123 --port 4269
```

To see all available options

```bash
python draw2img/main.py --help
```

### Container (Docker/Podman)

You can use the provided Dockerfile to build and run a container image:

```bash
DOCKER_BUILDKIT=1 docker build -t draw2img .
```

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.

```bash
docker run -it -p 8080:8080 -p 8079:8079 -v ~/.cache/huggingface:/root/.cache/huggingface --gpus all draw2img
```

# Development

## Server

Install the Python package in editable mode

```bash
pip install -e .
```

## UI

The UI can be built manually (static files are output to `dist` folder)

```bash
cd draw2img/ui
npm run build
```

Or alternatively, the Vue 3 template comes with a file server & hot reloading for easy development

```bash
npm run dev
```

### Container (Docker/Podman)

You can avoid installing `node` and `npm` on your host machine by using a container image with the UI development toolchain (`node:lts-slim`).

```bash
cd draw2img/ui
# build the UI
docker run -it -v $(pwd):/ui -p 5173:5173 node:lts-slim bash -c "cd ui && npm run build"
# or run the dev server
docker run -it -v $(pwd):/ui -p 5173:5173 node:lts-slim bash -c "cd ui && npm run dev -- --host"
```

## Design Notes

The backend is a multi-threaded Python websocket server, that also serves the static files for the web UI.

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.

### Performance

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.

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).

If you need additional concurrency and have available RAM/VRAM + compute, consider starting multiple instances of the `draw2img` process.

### Security

This code has not been audited for vulnerabilities.

# Contributions

Contributions are welcome! Please keep in mind the ethos of this project when opening PRs or issues.

# Safety

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.

# Non-goals / Other Projects

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.

# License

[MIT](LICENSE)

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).
1 change: 1 addition & 0 deletions draw2img/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import main
51 changes: 51 additions & 0 deletions draw2img/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import http.server
import logging
import os
import sys
import threading
from http.server import ThreadingHTTPServer
from threading import Thread

import fire

from draw2img.server import server

logger = logging.getLogger("draw2img")
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logging.getLogger("websockets.server").setLevel(logging.WARNING)
logging.getLogger("http.server").setLevel(logging.WARNING)
# path to static files for serving web UI
UI_DIR = os.path.join(os.path.dirname(__file__), "ui/dist")


def main(host: str = "localhost", port: int = 8080, dir: str = UI_DIR):
"""Starts a draw2img process, blocks until keyboard interrupt."""

try:
# start web socket server in a separate thread
wss_port = port - 1
stop_event: threading.Event = threading.Event()
thread = Thread(target=server, args=(host, wss_port, stop_event))
thread.start()

# start static file server for web UI
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=dir, **kwargs)

with ThreadingHTTPServer((host, port), Handler) as httpd:
logger.info(f"Web UI URL: http://{host}:{port}")
httpd.serve_forever()

except KeyboardInterrupt:
stop_event.set()
except Exception:
logger.exception("draw2img unexpected error")
finally:
thread.join()

return


if __name__ == "__main__":
fire.Fire(main)
1 change: 1 addition & 0 deletions draw2img/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import server
Loading

0 comments on commit b292b98

Please sign in to comment.