├── .bumpversion.cfg ├── .coveragerc ├── .editorconfig ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── .pylintrc ├── LICENSE ├── Makefile ├── README.md ├── easyai ├── __init__.py ├── base.py ├── controlnet.py ├── image.py ├── interfaces.py ├── main.py ├── result.py └── upscaler.py ├── easyai_demo.ipynb ├── pyproject.toml └── setup.cfg /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.2 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+) 6 | serialize = 7 | {major}.{feat}.{patch} 8 | 9 | [bumpversion:file:easyai/__init__.py] 10 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | pragma: no cover 4 | def __repr__ 5 | def __str__ 6 | if self.debug: 7 | if settings.DEBUG 8 | raise AssertionError 9 | raise NotImplementedError 10 | if 0: 11 | if __name__ == .__main__.: 12 | class .*\bProtocol\): 13 | @(abc\.)?abstractmethod 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml,xml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | 25 | [nginx.conf] 26 | indent_style = space 27 | indent_size = 2 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | ### Node template 133 | # Logs 134 | logs 135 | *.log 136 | npm-debug.log* 137 | yarn-debug.log* 138 | yarn-error.log* 139 | 140 | # Runtime data 141 | pids 142 | *.pid 143 | *.seed 144 | *.pid.lock 145 | 146 | # Directory for instrumented libs generated by jscoverage/JSCover 147 | lib-cov 148 | 149 | # Coverage directory used by tools like istanbul 150 | coverage 151 | 152 | # nyc test coverage 153 | .nyc_output 154 | 155 | # Bower dependency directory (https://bower.io/) 156 | bower_components 157 | 158 | # node-waf configuration 159 | .lock-wscript 160 | 161 | # Compiled binary addons (http://nodejs.org/api/addons.html) 162 | build/Release 163 | 164 | # Dependency directories 165 | node_modules/ 166 | jspm_packages/ 167 | 168 | # Typescript v1 declaration files 169 | typings/ 170 | 171 | # Optional npm cache directory 172 | .npm 173 | 174 | # Optional eslint cache 175 | .eslintcache 176 | 177 | # Optional REPL history 178 | .node_repl_history 179 | 180 | # Output of 'npm pack' 181 | *.tgz 182 | 183 | # Yarn Integrity file 184 | .yarn-integrity 185 | 186 | 187 | ### Linux template 188 | *~ 189 | 190 | # temporary files which can be created if a process still has a handle open of a deleted file 191 | .fuse_hidden* 192 | 193 | # KDE directory preferences 194 | .directory 195 | 196 | # Linux trash folder which might appear on any partition or disk 197 | .Trash-* 198 | 199 | # .nfs files are created when an open file is removed but is still being accessed 200 | .nfs* 201 | 202 | 203 | ### VisualStudioCode template 204 | .vscode/* 205 | !.vscode/settings.json 206 | !.vscode/tasks.json 207 | !.vscode/launch.json 208 | !.vscode/extensions.json 209 | *.code-workspace 210 | 211 | # Local History for Visual Studio Code 212 | .history/ 213 | 214 | 215 | # Provided default Pycharm Run/Debug Configurations should be tracked by git 216 | # In case of local modifications made by Pycharm, use update-index command 217 | # for each changed file, like this: 218 | # git update-index --assume-unchanged .idea/project.iml 219 | ### JetBrains template 220 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 221 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 222 | 223 | # User-specific stuff: 224 | .idea/**/workspace.xml 225 | .idea/**/tasks.xml 226 | .idea/dictionaries 227 | 228 | # Sensitive or high-churn files: 229 | .idea/**/dataSources/ 230 | .idea/**/dataSources.ids 231 | .idea/**/dataSources.xml 232 | .idea/**/dataSources.local.xml 233 | .idea/**/sqlDataSources.xml 234 | .idea/**/dynamic.xml 235 | .idea/**/uiDesigner.xml 236 | 237 | # Gradle: 238 | .idea/**/gradle.xml 239 | .idea/**/libraries 240 | 241 | # CMake 242 | cmake-build-debug/ 243 | 244 | # Mongo Explorer plugin: 245 | .idea/**/mongoSettings.xml 246 | 247 | ## File-based project format: 248 | *.iws 249 | 250 | ## Plugin-specific files: 251 | 252 | # IntelliJ 253 | out/ 254 | 255 | # mpeltonen/sbt-idea plugin 256 | .idea_modules/ 257 | 258 | # JIRA plugin 259 | atlassian-ide-plugin.xml 260 | 261 | # Cursive Clojure plugin 262 | .idea/replstate.xml 263 | 264 | # Crashlytics plugin (for Android Studio and IntelliJ) 265 | com_crashlytics_export_strings.xml 266 | crashlytics.properties 267 | crashlytics-build.properties 268 | fabric.properties 269 | 270 | 271 | 272 | ### Windows template 273 | # Windows thumbnail cache files 274 | Thumbs.db 275 | ehthumbs.db 276 | ehthumbs_vista.db 277 | 278 | # Dump file 279 | *.stackdump 280 | 281 | # Folder config file 282 | Desktop.ini 283 | 284 | # Recycle Bin used on file shares 285 | $RECYCLE.BIN/ 286 | 287 | # Windows Installer files 288 | *.cab 289 | *.msi 290 | *.msm 291 | *.msp 292 | 293 | # Windows shortcuts 294 | *.lnk 295 | 296 | 297 | ### macOS template 298 | # General 299 | *.DS_Store 300 | .AppleDouble 301 | .LSOverride 302 | 303 | # Icon must end with two \r 304 | Icon 305 | 306 | # Thumbnails 307 | ._* 308 | 309 | # Files that might appear in the root of a volume 310 | .DocumentRevisions-V100 311 | .fseventsd 312 | .Spotlight-V100 313 | .TemporaryItems 314 | .Trashes 315 | .VolumeIcon.icns 316 | .com.apple.timemachine.donotpresent 317 | 318 | # Directories potentially created on remote AFP share 319 | .AppleDB 320 | .AppleDesktop 321 | Network Trash Folder 322 | Temporary Items 323 | .apdisk 324 | 325 | 326 | ### SublimeText template 327 | # Cache files for Sublime Text 328 | *.tmlanguage.cache 329 | *.tmPreferences.cache 330 | *.stTheme.cache 331 | 332 | # Workspace files are user-specific 333 | *.sublime-workspace 334 | 335 | # Project files should be checked into the repository, unless a significant 336 | # proportion of contributors will probably not be using Sublime Text 337 | # *.sublime-project 338 | 339 | # SFTP configuration file 340 | sftp-config.json 341 | 342 | # Package control specific files 343 | Package Control.last-run 344 | Package Control.ca-list 345 | Package Control.ca-bundle 346 | Package Control.system-ca-bundle 347 | Package Control.cache/ 348 | Package Control.ca-certs/ 349 | Package Control.merged-ca-bundle 350 | Package Control.user-ca-bundle 351 | oscrypto-ca-bundle.crt 352 | bh_unicode_properties.cache 353 | 354 | # Sublime-github package stores a github token in this file 355 | # https://packagecontrol.io/packages/sublime-github 356 | GitHub.sublime-settings 357 | 358 | 359 | ### Vim template 360 | # Swap 361 | [._]*.s[a-v][a-z] 362 | [._]*.sw[a-p] 363 | [._]s[a-v][a-z] 364 | [._]sw[a-p] 365 | 366 | # Session 367 | Session.vim 368 | 369 | # Temporary 370 | .netrwhist 371 | 372 | # Auto-generated tag files 373 | tags 374 | 375 | ### Project template 376 | .pytest_cache/ 377 | .ipython/ 378 | 379 | 380 | .envs/* 381 | !.envs/.local/ 382 | /.idea 383 | /.idea/inspectionProfiles/profiles_settings.xml 384 | /.idea/inspectionProfiles/Project_Default.xml 385 | /.idea/.gitignore 386 | /.idea/*.iml 387 | /.idea/*.xml 388 | /.idea/modules.xml 389 | /.idea/vcs.xml 390 | /.idea/shelf/ 391 | /.idea/git_toolbox_prj.xml 392 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile = black 3 | combine_as_imports = true 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.3.0 4 | hooks: 5 | - id: check-merge-conflict 6 | 7 | - repo: https://github.com/asottile/yesqa 8 | rev: v1.3.0 9 | hooks: 10 | - id: yesqa 11 | 12 | - repo: https://github.com/pre-commit/pre-commit-hooks 13 | rev: v4.3.0 14 | hooks: 15 | - id: check-yaml 16 | - id: end-of-file-fixer 17 | exclude: '.bumpversion.cfg' 18 | - id: trailing-whitespace 19 | exclude: '.bumpversion.cfg' 20 | - id: requirements-txt-fixer 21 | - id: detect-private-key 22 | - id: detect-aws-credentials 23 | args: [--allow-missing-credentials] 24 | 25 | - repo: https://github.com/psf/black 26 | rev: 22.8.0 27 | hooks: 28 | - id: black 29 | 30 | - repo: https://github.com/PyCQA/isort 31 | rev: 5.10.1 32 | hooks: 33 | - id: isort 34 | 35 | - repo: https://github.com/PyCQA/flake8 36 | rev: 5.0.4 37 | hooks: 38 | - id: flake8 39 | args: ["--config=setup.cfg"] 40 | additional_dependencies: [flake8-isort] 41 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins= 3 | 4 | [FORMAT] 5 | max-line-length=88 6 | 7 | [MESSAGES CONTROL] 8 | disable=missing-docstring,invalid-name 9 | 10 | [DESIGN] 11 | max-parents=13 12 | 13 | [TYPECHECK] 14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 ChunKoo Park 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help docs 2 | .DEFAULT_GOAL := help 3 | 4 | help: 5 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 6 | 7 | clean: ## Removing cached python compiled files 8 | find . -name \*pyc | xargs rm -rfv 9 | find . -name \*pyo | xargs rm -fv 10 | find . -name \*~ | xargs rm -fv 11 | find . -name __pycache__ | xargs rm -rfv 12 | 13 | install: ## Install dependencies 14 | pip install flit 15 | make clean 16 | flit install --deps develop --symlink 17 | pre-commit install 18 | 19 | lint: ## Run code linters 20 | autoflake --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports -r easyai 21 | black --check easyai tests 22 | isort --check easyai tests 23 | flake8 24 | mypy easyai 25 | 26 | fmt format: ## Run code formatters 27 | autoflake --in-place --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports -r easyai 28 | isort easyai 29 | black easyai 30 | 31 | test: ## Run tests 32 | pytest -s -vv 33 | 34 | test-cov: ## Run tests with coverage 35 | pytest --cov=easyai --cov-report term 36 | 37 | test-cov-full: ## Run tests with coverage term-missing 38 | pytest --cov=easyai --cov-report term-missing tests 39 | 40 | doc-deploy: ## Run Deploy Documentation 41 | make clean 42 | mkdocs gh-deploy --force 43 | 44 | bump: 45 | bumpversion patch 46 | 47 | bump-feat: 48 | bumpversion feat 49 | 50 | bump-major: 51 | bumpversion major 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # easyai-sdwebui-api 2 | API client for AUTOMATIC1111/stable-diffusion-webui 3 | 4 | Tested on AUTOMATIC1111/stable-diffusion-webui v1.2.x/v1.4.x and Mikubill/sd-webui-controlnet v1.1.232 5 | 6 | * Supports txt2img, img2img, extra-single-image, extra-batch-images API calls. 7 | 8 | * API support have to be enabled from webui. Add --api when running webui. 9 | It's explained [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API). 10 | 11 | * You can use --api-auth user1:pass1,user2:pass2 option to enable authentication for api access. 12 | (Since it's basic http authentication the password is transmitted in cleartext) 13 | 14 | * API calls are (almost) direct translation from http://127.0.0.1:7860/docs as of 2022/11/21. 15 | 16 | # Install 17 | 18 | ``` 19 | pip install easyai-sdwebui-api 20 | ``` 21 | 22 | # Usage 23 | 24 | easyai_demo.ipynb contains example code with original images. Images are compressed as jpeg in this document. 25 | 26 | ## create API client 27 | ``` 28 | import easyai 29 | 30 | # create API client 31 | api = easyai.EasyAPI() 32 | 33 | # create API client with custom host, port 34 | #api = easyai.EasyAPI(host='127.0.0.1', port=7860) 35 | 36 | # create API client with custom host, port and https 37 | #api = easyai.EasyAPI(host='webui.example.com', port=443, use_https=True) 38 | 39 | # create API client with default sampler, steps. 40 | #api = easyai.EasyAPI(sampler='Euler a', steps=20) 41 | 42 | # optionally set username, password when --api-auth is set on webui. 43 | api.set_auth('username', 'password') 44 | 45 | # check controlnet version 46 | api.controlnet_version() 47 | 48 | # list all controlnet models 49 | # api.controlnet_module_list() 50 | # api.controlnet_model_list() 51 | 52 | ``` 53 | 54 | ## txt2img 55 | ``` 56 | result1 = api.txt2img(prompt="cute squirrel", 57 | negative_prompt="ugly, out of frame", 58 | seed=1003, 59 | styles=["anime"], 60 | cfg_scale=7, 61 | # sampler_index='DDIM', 62 | # steps=30, 63 | # enable_hr=True, 64 | # hr_scale=2, 65 | # hr_upscaler=easyai.HiResUpscaler.Latent, 66 | # hr_second_pass_steps=20, 67 | # hr_resize_x=1536, 68 | # hr_resize_y=1024, 69 | # denoising_strength=0.4, 70 | 71 | ) 72 | # images contains the returned images (PIL images) 73 | result1.images 74 | 75 | # image is shorthand for images[0] 76 | result1.image 77 | 78 | # info contains text info about the api call 79 | result1.info 80 | 81 | # info contains paramteres of the api call 82 | result1.parameters 83 | 84 | result1.image 85 | ``` 86 | 87 | 88 | ## img2img 89 | ``` 90 | result2 = api.img2img(images=[result1.image], prompt="cute cat", seed=5555, cfg_scale=6.5, denoising_strength=0.6) 91 | result2.image 92 | ``` 93 | 94 | ## img2img inpainting 95 | ``` 96 | from PIL import Image, ImageDraw 97 | 98 | mask = Image.new('RGB', result2.image.size, color = 'black') 99 | # mask = result2.image.copy() 100 | draw = ImageDraw.Draw(mask) 101 | draw.ellipse((210,150,310,250), fill='white') 102 | draw.ellipse((80,120,160,120+80), fill='white') 103 | 104 | mask 105 | ``` 106 | 107 | ``` 108 | inpainting_result = api.img2img(images=[result2.image], 109 | mask_image=mask, 110 | inpainting_fill=1, 111 | prompt="cute cat", 112 | seed=104, 113 | cfg_scale=5.0, 114 | denoising_strength=0.7) 115 | inpainting_result.image 116 | ``` 117 | 118 | ## extra-single-image 119 | ``` 120 | result3 = api.extra_single_image(image=result2.image, 121 | upscaler_1=easyai.Upscaler.ESRGAN_4x, 122 | upscaling_resize=1.5) 123 | print(result3.image.size) 124 | result3.image 125 | ``` 126 | (768, 768) 127 | 128 | 129 | ## extra-batch-images 130 | ``` 131 | result4 = api.extra_batch_images(images=[result1.image, inpainting_result.image], 132 | upscaler_1=easyai.Upscaler.ESRGAN_4x, 133 | upscaling_resize=1.5) 134 | result4.images[0] 135 | ``` 136 | ``` 137 | result4.images[1] 138 | ``` 139 | 140 | ### Async API support 141 | txt2img, img2img, extra_single_image, extra_batch_images support async api call with use_async=True parameter. You need asyncio, aiohttp packages installed. 142 | ``` 143 | result = await api.txt2img(prompt="cute kitten", 144 | seed=1001, 145 | use_async=True 146 | ) 147 | result.image 148 | ``` 149 | 150 | ### Scripts support 151 | Scripts from AUTOMATIC1111's Web UI are supported, but there aren't official models that define a script's interface. 152 | 153 | To find out the list of arguments that are accepted by a particular script look up the associated python file from 154 | AUTOMATIC1111's repo `scripts/[script_name].py`. Search for its `run(p, **args)` function and the arguments that come 155 | after 'p' is the list of accepted arguments 156 | 157 | #### Example for X/Y/Z Plot script: 158 | ``` 159 | (scripts/xyz_grid.py file from AUTOMATIC1111's repo) 160 | 161 | def run(self, p, x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size): 162 | ... 163 | ``` 164 | List of accepted arguments: 165 | * _x_type_: Index of the axis for X axis. Indexes start from [0: Nothing] 166 | * _x_values_: String of comma-separated values for the X axis 167 | * _y_type_: Index of the axis type for Y axis. As the X axis, indexes start from [0: Nothing] 168 | * _y_values_: String of comma-separated values for the Y axis 169 | * _z_type_: Index of the axis type for Z axis. As the X axis, indexes start from [0: Nothing] 170 | * _z_values_: String of comma-separated values for the Z axis 171 | * _draw_legend_: "True" or "False". IMPORTANT: It needs to be a string and not a Boolean value 172 | * _include_lone_images_: "True" or "False". IMPORTANT: It needs to be a string and not a Boolean value 173 | * _include_sub_grids_: "True" or "False". IMPORTANT: It needs to be a string and not a Boolean value 174 | * _no_fixed_seeds_: "True" or "False". IMPORTANT: It needs to be a string and not a Boolean value 175 | * margin_size: int value 176 | ``` 177 | # Available Axis options (Different for txt2img and img2img!) 178 | XYZPlotAvailableTxt2ImgScripts = [ 179 | "Nothing", 180 | "Seed", 181 | "Var. seed", 182 | "Var. strength", 183 | "Steps", 184 | "Hires steps", 185 | "CFG Scale", 186 | "Prompt S/R", 187 | "Prompt order", 188 | "Sampler", 189 | "Checkpoint name", 190 | "Sigma Churn", 191 | "Sigma min", 192 | "Sigma max", 193 | "Sigma noise", 194 | "Eta", 195 | "Clip skip", 196 | "Denoising", 197 | "Hires upscaler", 198 | "VAE", 199 | "Styles", 200 | ] 201 | 202 | XYZPlotAvailableImg2ImgScripts = [ 203 | "Nothing", 204 | "Seed", 205 | "Var. seed", 206 | "Var. strength", 207 | "Steps", 208 | "CFG Scale", 209 | "Image CFG Scale", 210 | "Prompt S/R", 211 | "Prompt order", 212 | "Sampler", 213 | "Checkpoint name", 214 | "Sigma Churn", 215 | "Sigma min", 216 | "Sigma max", 217 | "Sigma noise", 218 | "Eta", 219 | "Clip skip", 220 | "Denoising", 221 | "Cond. Image Mask Weight", 222 | "VAE", 223 | "Styles", 224 | ] 225 | 226 | # Example call 227 | XAxisType = "Steps" 228 | XAxisValues = "20,30" 229 | YAxisType = "Sampler" 230 | YAxisValues = "Euler a, LMS" 231 | ZAxisType = "Nothing" 232 | ZAxisValues = "" 233 | drawLegend = "True" 234 | includeLoneImages = "False" 235 | includeSubGrids = "False" 236 | noFixedSeeds = "False" 237 | marginSize = 0 238 | 239 | 240 | # x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size 241 | 242 | result = api.txt2img( 243 | prompt="cute girl with short brown hair in black t-shirt in animation style", 244 | seed=1003, 245 | script_name="X/Y/Z Plot", 246 | script_args=[ 247 | XYZPlotAvailableTxt2ImgScripts.index(XAxisType), 248 | XAxisValues, 249 | XYZPlotAvailableTxt2ImgScripts.index(YAxisType), 250 | YAxisValues, 251 | XYZPlotAvailableTxt2ImgScripts.index(ZAxisType), 252 | ZAxisValues, 253 | drawLegend, 254 | includeLoneImages, 255 | includeSubGrids, 256 | noFixedSeeds, 257 | marginSize, ] 258 | ) 259 | 260 | result.image 261 | ``` 262 | 263 | 264 | ### Configuration APIs 265 | ``` 266 | # return map of current options 267 | options = api.get_options() 268 | 269 | # change sd model 270 | options = {} 271 | options['sd_model_checkpoint'] = 'model.ckpt [7460a6fa]' 272 | api.set_options(options) 273 | 274 | # when calling set_options, do not pass all options returned by get_options(). 275 | # it makes webui unusable (2022/11/21). 276 | 277 | # get available sd models 278 | api.get_sd_models() 279 | 280 | # misc get apis 281 | api.get_samplers() 282 | api.get_cmd_flags() 283 | api.get_hypernetworks() 284 | api.get_face_restorers() 285 | api.get_realesrgan_models() 286 | api.get_prompt_styles() 287 | api.get_artist_categories() # deprecated ? 288 | api.get_artists() # deprecated ? 289 | api.get_progress() 290 | api.get_embeddings() 291 | api.get_cmd_flags() 292 | api.get_scripts() 293 | api.get_memory() 294 | 295 | # misc apis 296 | api.interrupt() 297 | api.skip() 298 | ``` 299 | 300 | ### Utility methods 301 | ``` 302 | # save current model name 303 | old_model = api.util_get_current_model() 304 | 305 | # get list of available models 306 | models = api.util_get_model_names() 307 | 308 | # set model (use exact name) 309 | api.util_set_model(models[0]) 310 | 311 | # set model (find closest match) 312 | api.util_set_model('robodiffusion') 313 | 314 | # wait for job complete 315 | api.util_wait_for_ready() 316 | 317 | ``` 318 | 319 | ### LORA and alwayson_scripts example 320 | 321 | ``` 322 | r = api.txt2img(prompt='photo of a cute girl with green hair shuimobysim __juice__', 323 | seed=1000, 324 | save_images=True, 325 | alwayson_scripts={"Simple wildcards":[]} # wildcards extension doesn't accept more parameters. 326 | ) 327 | r.image 328 | ``` 329 | 330 | ### Extension support - Model-Keyword 331 | ``` 332 | # https://github.com/mix1009/model-keyword 333 | mki = easyai.ModelKeywordInterface(api) 334 | mki.get_keywords() 335 | ``` 336 | ModelKeywordResult(keywords=['nousr robot'], model='robo-diffusion-v1.ckpt', oldhash='41fef4bd', match_source='model-keyword.txt') 337 | 338 | 339 | ### Extension support - Instruct-Pix2Pix 340 | ``` 341 | # Instruct-Pix2Pix extension is now deprecated and is now part of webui. 342 | # You can use normal img2img with image_cfg_scale when instruct-pix2pix model is loaded. 343 | r = api.img2img(prompt='sunset', images=[pil_img], cfg_scale=7.5, image_cfg_scale=1.5) 344 | r.image 345 | ``` 346 | 347 | ### Extension support - ControlNet 348 | ``` 349 | # https://github.com/Mikubill/sd-webui-controlnet 350 | # check controlnet version 351 | api.controlnet_version() 352 | 353 | # list all controlnet models 354 | # api.controlnet_module_list() 355 | # api.controlnet_model_list() 356 | ``` 357 |
358 | ['control_v11e_sd15_ip2p [c4bb465c]',
359 |  'control_v11e_sd15_shuffle [526bfdae]',
360 |  'control_v11f1p_sd15_depth [cfd03158]',
361 |  'control_v11p_sd15_canny [d14c016b]',
362 |  'control_v11p_sd15_inpaint [ebff9138]',
363 |  'control_v11p_sd15_lineart [43d4be0d]',
364 |  'control_v11p_sd15_mlsd [aca30ff0]',
365 |  'control_v11p_sd15_normalbae [316696f1]',
366 |  'control_v11p_sd15_openpose [cab727d4]',
367 |  'control_v11p_sd15_scribble [d4ba51ff]',
368 |  'control_v11p_sd15_seg [e1f51eb9]',
369 |  'control_v11p_sd15_softedge [a8575a2a]',
370 |  'control_v11p_sd15s2_lineart_anime [3825e83e]',
371 |  'control_v11u_sd15_tile [1f041471]']
372 |  
373 | 374 | ``` 375 | api.controlnet_version() 376 | api.controlnet_module_list() 377 | ``` 378 | 379 | ``` 380 | # normal txt2img 381 | r = api.txt2img(prompt="photo of a beautiful girl with blonde hair", height=512, seed=100) 382 | img = r.image 383 | img 384 | ``` 385 | 386 | ``` 387 | # txt2img with ControlNet 388 | unit1 = easyai.ControlNetUnit(input_image=img, module='canny', model='control_canny-fp16 [e3fe7712]') 389 | 390 | r = api.txt2img(prompt="photo of a beautiful girl", controlnet_units=[unit1]) 391 | r.image 392 | ``` 393 | 394 | 395 | ``` 396 | # img2img with multiple ControlNets 397 | unit1 = easyai.ControlNetUnit(input_image=img, module='canny', model='control_canny-fp16 [e3fe7712]') 398 | unit2 = easyai.ControlNetUnit(input_image=img, module='depth', model='control_depth-fp16 [400750f6]', weight=0.5) 399 | 400 | r2 = api.img2img(prompt="girl", 401 | images=[img], 402 | width=512, 403 | height=512, 404 | controlnet_units=[unit1, unit2], 405 | sampler_name="Euler a", 406 | cfg_scale=7, 407 | ) 408 | r2.image 409 | ``` 410 | 411 | ``` 412 | r2.images[1] 413 | ``` 414 | 415 | ``` 416 | r2.images[2] 417 | ``` 418 | -------------------------------------------------------------------------------- /easyai/__init__.py: -------------------------------------------------------------------------------- 1 | """Easy SDWebUI API - Easy API for SDWebUI, forked from mix1009/sdwebuiapi""" 2 | from .controlnet import ControlNetUnit 3 | from .interfaces import ( 4 | ControlNetInterface, 5 | InstructPix2PixInterface, 6 | ModelKeywordInterface, 7 | ) 8 | from .main import EasyAI, EasyAPI, easyai 9 | from .upscaler import HiResUpscaler, Upscaler 10 | 11 | __version__ = "0.1.5" 12 | 13 | __all__ = [ 14 | "easyai", 15 | "EasyAI", 16 | "EasyAPI", 17 | "ModelKeywordInterface", 18 | "InstructPix2PixInterface", 19 | "ControlNetInterface", 20 | "ControlNetUnit", 21 | "Upscaler", 22 | "HiResUpscaler", 23 | ] 24 | -------------------------------------------------------------------------------- /easyai/base.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | import json 4 | 5 | import requests 6 | from PIL import Image 7 | 8 | from .image import b64_img 9 | from .result import APIResult 10 | 11 | 12 | class WebUIAPIBase: 13 | has_controlnet = False 14 | 15 | def __init__( 16 | self, 17 | host="127.0.0.1", 18 | port=7860, 19 | baseurl=None, 20 | sampler="Euler a", 21 | steps=20, 22 | use_https=False, 23 | username=None, 24 | password=None, 25 | ): 26 | if baseurl is None: 27 | if use_https: 28 | baseurl = f"https://{host}:{port}/sdapi/v1" 29 | else: 30 | baseurl = f"http://{host}:{port}/sdapi/v1" 31 | 32 | self.baseurl = baseurl 33 | self.default_sampler = sampler 34 | self.default_steps = steps 35 | 36 | self.session = requests.Session() 37 | 38 | if username and password: 39 | self.set_auth(username, password) 40 | else: 41 | self.check_controlnet() 42 | 43 | def check_controlnet(self): 44 | try: 45 | scripts = self.get_scripts() 46 | self.has_controlnet = "controlnet m2m" in scripts["txt2img"] 47 | except: # NOQA 48 | pass 49 | 50 | def set_auth(self, username, password): 51 | self.session.auth = (username, password) 52 | 53 | def post_and_get_api_result(self, url, json, use_async): 54 | if use_async: 55 | import asyncio 56 | 57 | return asyncio.ensure_future(self.async_post(url=url, json=json)) 58 | else: 59 | response = self.session.post(url=url, json=json) 60 | return self._to_api_result(response) 61 | 62 | async def async_post(self, url, json): 63 | import aiohttp 64 | 65 | async with aiohttp.ClientSession() as session: 66 | auth = ( 67 | aiohttp.BasicAuth(self.session.auth[0], self.session.auth[1]) 68 | if self.session.auth 69 | else None 70 | ) 71 | async with session.post(url, json=json, auth=auth) as response: 72 | return await self._to_api_result_async(response) 73 | 74 | def _extract_result(self, json_result): 75 | images = [] 76 | if "images" in json_result.keys(): 77 | images = [ 78 | Image.open(io.BytesIO(base64.b64decode(i))) 79 | for i in json_result["images"] 80 | ] 81 | elif "image" in json_result.keys(): 82 | images = [Image.open(io.BytesIO(base64.b64decode(json_result["image"])))] 83 | 84 | info = "" 85 | if "info" in json_result.keys(): 86 | try: 87 | info = json.loads(json_result["info"]) 88 | except: # NOQA 89 | info = json_result["info"] 90 | elif "html_info" in json_result.keys(): 91 | info = json_result["html_info"] 92 | elif "caption" in json_result.keys(): 93 | info = json_result["caption"] 94 | 95 | parameters = "" 96 | if "parameters" in json_result.keys(): 97 | parameters = json_result["parameters"] 98 | 99 | return APIResult(images, parameters, info) 100 | 101 | def _to_api_result(self, response): 102 | if response.status != 200: 103 | raise RuntimeError(response.status, response.text()) 104 | 105 | r = response.json() 106 | return self._extract_result(r) 107 | 108 | async def _to_api_result_async(self, response): 109 | if response.status != 200: 110 | raise RuntimeError(response.status, await response.text()) 111 | 112 | r = await response.json() 113 | return self._extract_result(r) 114 | 115 | # XXX 500 error (2022/12/26) 116 | def png_info(self, image): 117 | payload = { 118 | "image": b64_img(image), 119 | } 120 | 121 | response = self.session.post(url=f"{self.baseurl}/png-info", json=payload) 122 | return self._to_api_result(response) 123 | 124 | # XXX always returns empty info (2022/12/26) 125 | def interrogate(self, image): 126 | payload = { 127 | "image": b64_img(image), 128 | } 129 | 130 | response = self.session.post(url=f"{self.baseurl}/interrogate", json=payload) 131 | return self._to_api_result(response) 132 | 133 | def interrupt(self): 134 | response = self.session.post(url=f"{self.baseurl}/interrupt") 135 | return response.json() 136 | 137 | def skip(self): 138 | response = self.session.post(url=f"{self.baseurl}/skip") 139 | return response.json() 140 | 141 | def get_options(self): 142 | response = self.session.get(url=f"{self.baseurl}/options") 143 | return response.json() 144 | 145 | def set_options(self, options): 146 | response = self.session.post(url=f"{self.baseurl}/options", json=options) 147 | return response.json() 148 | 149 | def get_cmd_flags(self): 150 | response = self.session.get(url=f"{self.baseurl}/cmd-flags") 151 | return response.json() 152 | 153 | def get_progress(self): 154 | response = self.session.get(url=f"{self.baseurl}/progress") 155 | return response.json() 156 | 157 | def get_samplers(self): 158 | response = self.session.get(url=f"{self.baseurl}/samplers") 159 | return response.json() 160 | 161 | def get_upscalers(self): 162 | response = self.session.get(url=f"{self.baseurl}/upscalers") 163 | return response.json() 164 | 165 | def get_sd_models(self): 166 | response = self.session.get(url=f"{self.baseurl}/sd-models") 167 | return response.json() 168 | 169 | def get_hypernetworks(self): 170 | response = self.session.get(url=f"{self.baseurl}/hypernetworks") 171 | return response.json() 172 | 173 | def get_face_restorers(self): 174 | response = self.session.get(url=f"{self.baseurl}/face-restorers") 175 | return response.json() 176 | 177 | def get_realesrgan_models(self): 178 | response = self.session.get(url=f"{self.baseurl}/realesrgan-models") 179 | return response.json() 180 | 181 | def get_prompt_styles(self): 182 | response = self.session.get(url=f"{self.baseurl}/prompt-styles") 183 | return response.json() 184 | 185 | def get_artist_categories(self): # deprecated ? 186 | response = self.session.get(url=f"{self.baseurl}/artist-categories") 187 | return response.json() 188 | 189 | def get_artists(self): # deprecated ? 190 | response = self.session.get(url=f"{self.baseurl}/artists") 191 | return response.json() 192 | 193 | def refresh_checkpoints(self): 194 | response = self.session.post(url=f"{self.baseurl}/refresh-checkpoints") 195 | return response.json() 196 | 197 | def get_scripts(self): 198 | response = self.session.get(url=f"{self.baseurl}/scripts") 199 | return response.json() 200 | 201 | def get_embeddings(self): 202 | response = self.session.get(url=f"{self.baseurl}/embeddings") 203 | return response.json() 204 | 205 | def get_memory(self): 206 | response = self.session.get(url=f"{self.baseurl}/memory") 207 | return response.json() 208 | 209 | def get_endpoint(self, endpoint, baseurl): 210 | if baseurl: 211 | return f"{self.baseurl}/{endpoint}" 212 | else: 213 | from urllib.parse import urlparse, urlunparse 214 | 215 | parsed_url = urlparse(self.baseurl) 216 | basehost = parsed_url.netloc 217 | parsed_url2 = (parsed_url[0], basehost, endpoint, "", "", "") 218 | return urlunparse(parsed_url2) 219 | 220 | def custom_get(self, endpoint, baseurl=False): 221 | url = self.get_endpoint(endpoint, baseurl) 222 | response = self.session.get(url=url) 223 | return response.json() 224 | 225 | def custom_post(self, endpoint, payload={}, baseurl=False, use_async=False): 226 | url = self.get_endpoint(endpoint, baseurl) 227 | if use_async: 228 | import asyncio 229 | 230 | return asyncio.ensure_future(self.async_post(url=url, json=payload)) 231 | else: 232 | response = self.session.post(url=url, json=payload) 233 | return self._to_api_result(response) 234 | 235 | def controlnet_version(self): 236 | r = self.custom_get("controlnet/version") 237 | return r["version"] 238 | 239 | def controlnet_model_list(self): 240 | r = self.custom_get("controlnet/model_list") 241 | return r["model_list"] 242 | 243 | def controlnet_module_list(self): 244 | r = self.custom_get("controlnet/module_list") 245 | return r["module_list"] 246 | 247 | def controlnet_detect( 248 | self, images, module="none", processor_res=512, threshold_a=64, threshold_b=64 249 | ): 250 | input_images = [b64_img(x) for x in images] 251 | payload = { 252 | "controlnet_module": module, 253 | "controlnet_input_images": input_images, 254 | "controlnet_processor_res": processor_res, 255 | "controlnet_threshold_a": threshold_a, 256 | "controlnet_threshold_b": threshold_b, 257 | } 258 | r = self.custom_post("controlnet/detect", payload=payload) 259 | return r 260 | 261 | def util_get_model_names(self): 262 | return sorted([x["title"] for x in self.get_sd_models()]) 263 | 264 | def util_set_model(self, name, find_closest=True): 265 | if find_closest: 266 | name = name.lower() 267 | models = self.util_get_model_names() 268 | found_model = None 269 | if name in models: 270 | found_model = name 271 | elif find_closest: 272 | import difflib 273 | 274 | def str_simularity(a, b): 275 | return difflib.SequenceMatcher(None, a, b).ratio() 276 | 277 | max_sim = 0.0 278 | max_model = models[0] 279 | for model in models: 280 | sim = str_simularity(name, model) 281 | if sim >= max_sim: 282 | max_sim = sim 283 | max_model = model 284 | found_model = max_model 285 | if found_model: 286 | print(f"loading {found_model}") 287 | options = {} 288 | options["sd_model_checkpoint"] = found_model 289 | self.set_options(options) 290 | print(f"model changed to {found_model}") 291 | else: 292 | print("model not found") 293 | 294 | def util_get_current_model(self): 295 | return self.get_options()["sd_model_checkpoint"] 296 | 297 | def util_wait_for_ready(self, check_interval=5.0): 298 | import time 299 | 300 | while True: 301 | result = self.get_progress() 302 | progress = result["progress"] 303 | job_count = result["state"]["job_count"] 304 | if progress == 0.0 and job_count == 0: 305 | break 306 | else: 307 | print(f"[WAIT]: progress = {progress:.4f}, job_count = {job_count}") 308 | time.sleep(check_interval) 309 | -------------------------------------------------------------------------------- /easyai/controlnet.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from .image import raw_b64_img 4 | 5 | 6 | # https://github.com/Mikubill/sd-webui-controlnet 7 | class ControlNetUnit: 8 | def __init__( 9 | self, 10 | input_image: Image = None, 11 | mask: Image = None, 12 | module: str = "none", 13 | model: str = "None", 14 | weight: float = 1.0, 15 | resize_mode: str = "Resize and Fill", 16 | lowvram: bool = False, 17 | processor_res: int = 512, 18 | threshold_a: float = 0.1, 19 | threshold_b: float = 0.1, 20 | guidance: float = 1.0, 21 | guidance_start: float = 0.0, 22 | guidance_end: float = 1.0, 23 | control_mode: int = 0, 24 | pixel_perfect: bool = False, 25 | guessmode: int = None, # deprecated: use control_mode 26 | ): 27 | self.input_image = input_image 28 | self.mask = mask 29 | self.module = module 30 | self.model = model 31 | self.weight = weight 32 | self.resize_mode = resize_mode 33 | self.lowvram = lowvram 34 | self.processor_res = processor_res 35 | self.threshold_a = threshold_a 36 | self.threshold_b = threshold_b 37 | self.guidance = guidance 38 | self.guidance_start = guidance_start 39 | self.guidance_end = guidance_end 40 | if guessmode: 41 | print( 42 | "ControlNetUnit guessmode is deprecated. Please use control_mode instead." 43 | ) 44 | control_mode = guessmode 45 | self.control_mode = control_mode 46 | self.pixel_perfect = pixel_perfect 47 | 48 | def to_dict(self): 49 | return { 50 | "input_image": raw_b64_img(self.input_image) if self.input_image else "", 51 | "mask": raw_b64_img(self.mask) if self.mask is not None else None, 52 | "module": self.module, 53 | "model": self.model, 54 | "weight": self.weight, 55 | "resize_mode": self.resize_mode, 56 | "lowvram": self.lowvram, 57 | "processor_res": self.processor_res, 58 | "threshold_a": self.threshold_a, 59 | "threshold_b": self.threshold_b, 60 | "guidance": self.guidance, 61 | "guidance_start": self.guidance_start, 62 | "guidance_end": self.guidance_end, 63 | "control_mode": self.control_mode, 64 | "pixel_perfect": self.pixel_perfect, 65 | } 66 | -------------------------------------------------------------------------------- /easyai/image.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | 4 | from PIL import Image, PngImagePlugin 5 | 6 | 7 | def b64_img(image: Image) -> str: 8 | return "data:image/png;base64," + raw_b64_img(image) 9 | 10 | 11 | def raw_b64_img(image: Image) -> str: 12 | # XXX controlnet only accepts RAW base64 without headers 13 | with io.BytesIO() as output_bytes: 14 | metadata = None 15 | for key, value in image.info.items(): 16 | if isinstance(key, str) and isinstance(value, str): 17 | if metadata is None: 18 | metadata = PngImagePlugin.PngInfo() 19 | metadata.add_text(key, value) 20 | image.save(output_bytes, format="PNG", pnginfo=metadata) 21 | 22 | bytes_data = output_bytes.getvalue() 23 | 24 | return str(base64.b64encode(bytes_data), "utf-8") 25 | -------------------------------------------------------------------------------- /easyai/interfaces.py: -------------------------------------------------------------------------------- 1 | """Interface for extensions""" 2 | from dataclasses import dataclass 3 | from typing import Any, Dict 4 | 5 | from .image import b64_img, raw_b64_img 6 | 7 | 8 | @dataclass 9 | class ModelKeywordResult: 10 | keywords: list 11 | model: str 12 | oldhash: str 13 | match_source: str 14 | 15 | 16 | class ModelKeywordInterface: 17 | """https://github.com/mix1009/model-keyword""" 18 | 19 | def __init__(self, easyai): 20 | self.api = easyai 21 | 22 | def get_keywords(self): 23 | result = self.api.custom_get("model_keyword/get_keywords") 24 | keywords = result["keywords"] 25 | model = result["model"] 26 | oldhash = result["hash"] 27 | match_source = result["match_source"] 28 | return ModelKeywordResult(keywords, model, oldhash, match_source) 29 | 30 | 31 | class InstructPix2PixInterface: 32 | """https://github.com/Klace/stable-diffusion-webui-instruct-pix2pix""" 33 | 34 | def __init__(self, easyai): 35 | self.api = easyai 36 | 37 | def img2img( 38 | self, 39 | images=[], 40 | prompt: str = "", 41 | negative_prompt: str = "", 42 | output_batches: int = 1, 43 | sampler: str = "Euler a", 44 | steps: int = 20, 45 | seed: int = 0, 46 | randomize_seed: bool = True, 47 | text_cfg: float = 7.5, 48 | image_cfg: float = 1.5, 49 | randomize_cfg: bool = False, 50 | output_image_width: int = 512, 51 | ): 52 | init_images = [b64_img(x) for x in images] 53 | payload = { 54 | "init_images": init_images, 55 | "prompt": prompt, 56 | "negative_prompt": negative_prompt, 57 | "output_batches": output_batches, 58 | "sampler": sampler, 59 | "steps": steps, 60 | "seed": seed, 61 | "randomize_seed": randomize_seed, 62 | "text_cfg": text_cfg, 63 | "image_cfg": image_cfg, 64 | "randomize_cfg": randomize_cfg, 65 | "output_image_width": output_image_width, 66 | } 67 | return self.api.custom_post("instruct-pix2pix/img2img", payload=payload) 68 | 69 | 70 | class ControlNetInterface: 71 | def __init__(self, easyai, show_deprecation_warning=True): 72 | self.api = easyai 73 | self.show_deprecation_warning = show_deprecation_warning 74 | 75 | def print_deprecation_warning(self): 76 | print( 77 | "ControlNetInterface txt2img/img2img is deprecated. Please use normal txt2img/img2img with controlnet_units param" 78 | ) 79 | 80 | def txt2img( 81 | self, 82 | prompt: str = "", 83 | negative_prompt: str = "", 84 | controlnet_input_image: [] = [], 85 | controlnet_mask: [] = [], 86 | controlnet_module: str = "", 87 | controlnet_model: str = "", 88 | controlnet_weight: float = 0.5, 89 | controlnet_resize_mode: str = "Scale to Fit (Inner Fit)", 90 | controlnet_lowvram: bool = False, 91 | controlnet_processor_res: int = 512, 92 | controlnet_threshold_a: int = 64, 93 | controlnet_threshold_b: int = 64, 94 | controlnet_guidance: float = 1.0, 95 | enable_hr: bool = False, # hiresfix 96 | denoising_strength: float = 0.5, 97 | hr_scale: float = 1.5, 98 | hr_upscale: str = "Latent", 99 | guess_mode: bool = True, 100 | seed: int = -1, 101 | subseed: int = -1, 102 | subseed_strength: int = -1, 103 | sampler_index: str = "Euler a", 104 | batch_size: int = 1, 105 | n_iter: int = 1, # Iteration 106 | steps: int = 20, 107 | cfg_scale: float = 7, 108 | width: int = 512, 109 | height: int = 512, 110 | restore_faces: bool = False, 111 | override_settings: Dict[str, Any] = None, 112 | override_settings_restore_afterwards: bool = True, 113 | ): 114 | if self.show_deprecation_warning: 115 | self.print_deprecation_warning() 116 | 117 | controlnet_input_image_b64 = [raw_b64_img(x) for x in controlnet_input_image] 118 | controlnet_mask_b64 = [raw_b64_img(x) for x in controlnet_mask] 119 | 120 | payload = { 121 | "prompt": prompt, 122 | "negative_prompt": negative_prompt, 123 | "controlnet_input_image": controlnet_input_image_b64, 124 | "controlnet_mask": controlnet_mask_b64, 125 | "controlnet_module": controlnet_module, 126 | "controlnet_model": controlnet_model, 127 | "controlnet_weight": controlnet_weight, 128 | "controlnet_resize_mode": controlnet_resize_mode, 129 | "controlnet_lowvram": controlnet_lowvram, 130 | "controlnet_processor_res": controlnet_processor_res, 131 | "controlnet_threshold_a": controlnet_threshold_a, 132 | "controlnet_threshold_b": controlnet_threshold_b, 133 | "controlnet_guidance": controlnet_guidance, 134 | "enable_hr": enable_hr, 135 | "denoising_strength": denoising_strength, 136 | "hr_scale": hr_scale, 137 | "hr_upscale": hr_upscale, 138 | "guess_mode": guess_mode, 139 | "seed": seed, 140 | "subseed": subseed, 141 | "subseed_strength": subseed_strength, 142 | "sampler_index": sampler_index, 143 | "batch_size": batch_size, 144 | "n_iter": n_iter, 145 | "steps": steps, 146 | "cfg_scale": cfg_scale, 147 | "width": width, 148 | "height": height, 149 | "restore_faces": restore_faces, 150 | "override_settings": override_settings, 151 | "override_settings_restore_afterwards": override_settings_restore_afterwards, 152 | } 153 | return self.api.custom_post("controlnet/txt2img", payload=payload) 154 | 155 | def img2img( 156 | self, 157 | init_images: [] = [], 158 | mask: str = None, 159 | mask_blur: int = 30, 160 | inpainting_fill: int = 0, 161 | inpaint_full_res: bool = True, 162 | inpaint_full_res_padding: int = 1, 163 | inpainting_mask_invert: int = 1, 164 | resize_mode: int = 0, 165 | denoising_strength: float = 0.7, 166 | prompt: str = "", 167 | negative_prompt: str = "", 168 | controlnet_input_image: [] = [], 169 | controlnet_mask: [] = [], 170 | controlnet_module: str = "", 171 | controlnet_model: str = "", 172 | controlnet_weight: float = 1.0, 173 | controlnet_resize_mode: str = "Scale to Fit (Inner Fit)", 174 | controlnet_lowvram: bool = False, 175 | controlnet_processor_res: int = 512, 176 | controlnet_threshold_a: int = 64, 177 | controlnet_threshold_b: int = 64, 178 | controlnet_guidance: float = 1.0, 179 | guess_mode: bool = True, 180 | seed: int = -1, 181 | subseed: int = -1, 182 | subseed_strength: int = -1, 183 | sampler_index: str = "", 184 | batch_size: int = 1, 185 | n_iter: int = 1, # Iteration 186 | steps: int = 20, 187 | cfg_scale: float = 7, 188 | width: int = 512, 189 | height: int = 512, 190 | restore_faces: bool = False, 191 | include_init_images: bool = True, 192 | override_settings: Dict[str, Any] = None, 193 | override_settings_restore_afterwards: bool = True, 194 | ): 195 | if self.show_deprecation_warning: 196 | self.print_deprecation_warning() 197 | 198 | init_images_b64 = [raw_b64_img(x) for x in init_images] 199 | controlnet_input_image_b64 = [raw_b64_img(x) for x in controlnet_input_image] 200 | controlnet_mask_b64 = [raw_b64_img(x) for x in controlnet_mask] 201 | 202 | payload = { 203 | "init_images": init_images_b64, 204 | "mask": raw_b64_img(mask) if mask else None, 205 | "mask_blur": mask_blur, 206 | "inpainting_fill": inpainting_fill, 207 | "inpaint_full_res": inpaint_full_res, 208 | "inpaint_full_res_padding": inpaint_full_res_padding, 209 | "inpainting_mask_invert": inpainting_mask_invert, 210 | "resize_mode": resize_mode, 211 | "denoising_strength": denoising_strength, 212 | "prompt": prompt, 213 | "negative_prompt": negative_prompt, 214 | "controlnet_input_image": controlnet_input_image_b64, 215 | "controlnet_mask": controlnet_mask_b64, 216 | "controlnet_module": controlnet_module, 217 | "controlnet_model": controlnet_model, 218 | "controlnet_weight": controlnet_weight, 219 | "controlnet_resize_mode": controlnet_resize_mode, 220 | "controlnet_lowvram": controlnet_lowvram, 221 | "controlnet_processor_res": controlnet_processor_res, 222 | "controlnet_threshold_a": controlnet_threshold_a, 223 | "controlnet_threshold_b": controlnet_threshold_b, 224 | "controlnet_guidance": controlnet_guidance, 225 | "guess_mode": guess_mode, 226 | "seed": seed, 227 | "subseed": subseed, 228 | "subseed_strength": subseed_strength, 229 | "sampler_index": sampler_index, 230 | "batch_size": batch_size, 231 | "n_iter": n_iter, 232 | "steps": steps, 233 | "cfg_scale": cfg_scale, 234 | "width": width, 235 | "height": height, 236 | "restore_faces": restore_faces, 237 | "include_init_images": include_init_images, 238 | "override_settings": override_settings, 239 | "override_settings_restore_afterwards": override_settings_restore_afterwards, 240 | } 241 | return self.api.custom_post("controlnet/img2img", payload=payload) 242 | 243 | def model_list(self): 244 | r = self.api.custom_get("controlnet/model_list") 245 | return r["model_list"] 246 | -------------------------------------------------------------------------------- /easyai/main.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from .base import WebUIAPIBase 4 | from .controlnet import ControlNetUnit 5 | from .image import b64_img 6 | from .upscaler import HiResUpscaler 7 | 8 | 9 | class EasyAPI(WebUIAPIBase): 10 | def txt2img( 11 | self, 12 | enable_hr=False, 13 | denoising_strength=0.7, 14 | firstphase_width=0, 15 | firstphase_height=0, 16 | hr_scale=2, 17 | hr_upscaler=HiResUpscaler.Latent, 18 | hr_second_pass_steps=0, 19 | hr_resize_x=0, 20 | hr_resize_y=0, 21 | prompt="", 22 | styles=[], 23 | seed=-1, 24 | subseed=-1, 25 | subseed_strength=0.0, 26 | seed_resize_from_h=0, 27 | seed_resize_from_w=0, 28 | sampler_name=None, # use this instead of sampler_index 29 | batch_size=1, 30 | n_iter=1, 31 | steps=None, 32 | cfg_scale=7.0, 33 | width=512, 34 | height=512, 35 | restore_faces=False, 36 | tiling=False, 37 | do_not_save_samples=False, 38 | do_not_save_grid=False, 39 | negative_prompt="", 40 | eta=1.0, 41 | s_churn=0, 42 | s_tmax=0, 43 | s_tmin=0, 44 | s_noise=1, 45 | override_settings={}, 46 | override_settings_restore_afterwards=True, 47 | script_args=None, # List of arguments for the script "script_name" 48 | script_name=None, 49 | send_images=True, 50 | save_images=False, 51 | alwayson_scripts={}, 52 | controlnet_units: List[ControlNetUnit] = [], 53 | sampler_index=None, # deprecated: use sampler_name 54 | use_deprecated_controlnet=False, 55 | use_async=False, 56 | ): 57 | if sampler_index is None: 58 | sampler_index = self.default_sampler 59 | if sampler_name is None: 60 | sampler_name = self.default_sampler 61 | if steps is None: 62 | steps = self.default_steps 63 | if script_args is None: 64 | script_args = [] 65 | payload = { 66 | "enable_hr": enable_hr, 67 | "hr_scale": hr_scale, 68 | "hr_upscaler": hr_upscaler, 69 | "hr_second_pass_steps": hr_second_pass_steps, 70 | "hr_resize_x": hr_resize_x, 71 | "hr_resize_y": hr_resize_y, 72 | "denoising_strength": denoising_strength, 73 | "firstphase_width": firstphase_width, 74 | "firstphase_height": firstphase_height, 75 | "prompt": prompt, 76 | "styles": styles, 77 | "seed": seed, 78 | "subseed": subseed, 79 | "subseed_strength": subseed_strength, 80 | "seed_resize_from_h": seed_resize_from_h, 81 | "seed_resize_from_w": seed_resize_from_w, 82 | "batch_size": batch_size, 83 | "n_iter": n_iter, 84 | "steps": steps, 85 | "cfg_scale": cfg_scale, 86 | "width": width, 87 | "height": height, 88 | "restore_faces": restore_faces, 89 | "tiling": tiling, 90 | "do_not_save_samples": do_not_save_samples, 91 | "do_not_save_grid": do_not_save_grid, 92 | "negative_prompt": negative_prompt, 93 | "eta": eta, 94 | "s_churn": s_churn, 95 | "s_tmax": s_tmax, 96 | "s_tmin": s_tmin, 97 | "s_noise": s_noise, 98 | "override_settings": override_settings, 99 | "override_settings_restore_afterwards": override_settings_restore_afterwards, 100 | "sampler_name": sampler_name, 101 | "sampler_index": sampler_index, 102 | "script_name": script_name, 103 | "script_args": script_args, 104 | "send_images": send_images, 105 | "save_images": save_images, 106 | "alwayson_scripts": alwayson_scripts, 107 | } 108 | 109 | if use_deprecated_controlnet and controlnet_units and len(controlnet_units) > 0: 110 | payload["controlnet_units"] = [x.to_dict() for x in controlnet_units] 111 | return self.custom_post("controlnet/txt2img", payload=payload) 112 | 113 | if controlnet_units and len(controlnet_units) > 0: 114 | payload["alwayson_scripts"]["ControlNet"] = { 115 | "args": [x.to_dict() for x in controlnet_units] 116 | } 117 | 118 | elif self.has_controlnet: 119 | # workaround : if not passed, webui will use previous args! 120 | payload["alwayson_scripts"]["ControlNet"] = {"args": []} 121 | 122 | return self.post_and_get_api_result( 123 | f"{self.baseurl}/txt2img", payload, use_async 124 | ) 125 | 126 | def img2img( 127 | self, 128 | images=[], # list of PIL Image 129 | resize_mode=0, 130 | denoising_strength=0.75, 131 | image_cfg_scale=1.5, 132 | mask_image=None, # PIL Image mask 133 | mask_blur=4, 134 | inpainting_fill=0, 135 | inpaint_full_res=True, 136 | inpaint_full_res_padding=0, 137 | inpainting_mask_invert=0, 138 | initial_noise_multiplier=1, 139 | prompt="", 140 | styles=[], 141 | seed=-1, 142 | subseed=-1, 143 | subseed_strength=0, 144 | seed_resize_from_h=0, 145 | seed_resize_from_w=0, 146 | sampler_name=None, # use this instead of sampler_index 147 | batch_size=1, 148 | n_iter=1, 149 | steps=None, 150 | cfg_scale=7.0, 151 | width=512, 152 | height=512, 153 | restore_faces=False, 154 | tiling=False, 155 | do_not_save_samples=False, 156 | do_not_save_grid=False, 157 | negative_prompt="", 158 | eta=1.0, 159 | s_churn=0, 160 | s_tmax=0, 161 | s_tmin=0, 162 | s_noise=1, 163 | override_settings={}, 164 | override_settings_restore_afterwards=True, 165 | script_args=None, # List of arguments for the script "script_name" 166 | sampler_index=None, # deprecated: use sampler_name 167 | include_init_images=False, 168 | script_name=None, 169 | send_images=True, 170 | save_images=False, 171 | alwayson_scripts={}, 172 | controlnet_units: List[ControlNetUnit] = [], 173 | use_deprecated_controlnet=False, 174 | use_async=False, 175 | ): 176 | if sampler_name is None: 177 | sampler_name = self.default_sampler 178 | if sampler_index is None: 179 | sampler_index = self.default_sampler 180 | if steps is None: 181 | steps = self.default_steps 182 | if script_args is None: 183 | script_args = [] 184 | 185 | payload = { 186 | "init_images": [b64_img(x) for x in images], 187 | "resize_mode": resize_mode, 188 | "denoising_strength": denoising_strength, 189 | "mask_blur": mask_blur, 190 | "inpainting_fill": inpainting_fill, 191 | "inpaint_full_res": inpaint_full_res, 192 | "inpaint_full_res_padding": inpaint_full_res_padding, 193 | "inpainting_mask_invert": inpainting_mask_invert, 194 | "initial_noise_multiplier": initial_noise_multiplier, 195 | "prompt": prompt, 196 | "styles": styles, 197 | "seed": seed, 198 | "subseed": subseed, 199 | "subseed_strength": subseed_strength, 200 | "seed_resize_from_h": seed_resize_from_h, 201 | "seed_resize_from_w": seed_resize_from_w, 202 | "batch_size": batch_size, 203 | "n_iter": n_iter, 204 | "steps": steps, 205 | "cfg_scale": cfg_scale, 206 | "image_cfg_scale": image_cfg_scale, 207 | "width": width, 208 | "height": height, 209 | "restore_faces": restore_faces, 210 | "tiling": tiling, 211 | "do_not_save_samples": do_not_save_samples, 212 | "do_not_save_grid": do_not_save_grid, 213 | "negative_prompt": negative_prompt, 214 | "eta": eta, 215 | "s_churn": s_churn, 216 | "s_tmax": s_tmax, 217 | "s_tmin": s_tmin, 218 | "s_noise": s_noise, 219 | "override_settings": override_settings, 220 | "override_settings_restore_afterwards": override_settings_restore_afterwards, 221 | "sampler_name": sampler_name, 222 | "sampler_index": sampler_index, 223 | "include_init_images": include_init_images, 224 | "script_name": script_name, 225 | "script_args": script_args, 226 | "send_images": send_images, 227 | "save_images": save_images, 228 | "alwayson_scripts": alwayson_scripts, 229 | } 230 | if mask_image is not None: 231 | payload["mask"] = b64_img(mask_image) 232 | 233 | if use_deprecated_controlnet and controlnet_units and len(controlnet_units) > 0: 234 | payload["controlnet_units"] = [x.to_dict() for x in controlnet_units] 235 | return self.custom_post("controlnet/img2img", payload=payload) 236 | 237 | if controlnet_units and len(controlnet_units) > 0: 238 | payload["alwayson_scripts"]["ControlNet"] = { 239 | "args": [x.to_dict() for x in controlnet_units] 240 | } 241 | elif self.has_controlnet: 242 | payload["alwayson_scripts"]["ControlNet"] = {"args": []} 243 | 244 | return self.post_and_get_api_result( 245 | f"{self.baseurl}/img2img", payload, use_async 246 | ) 247 | 248 | def extra_single_image( 249 | self, 250 | image, # PIL Image 251 | resize_mode=0, 252 | show_extras_results=True, 253 | gfpgan_visibility=0, 254 | codeformer_visibility=0, 255 | codeformer_weight=0, 256 | upscaling_resize=2, 257 | upscaling_resize_w=512, 258 | upscaling_resize_h=512, 259 | upscaling_crop=True, 260 | upscaler_1="None", 261 | upscaler_2="None", 262 | extras_upscaler_2_visibility=0, 263 | upscale_first=False, 264 | use_async=False, 265 | ): 266 | payload = { 267 | "resize_mode": resize_mode, 268 | "show_extras_results": show_extras_results, 269 | "gfpgan_visibility": gfpgan_visibility, 270 | "codeformer_visibility": codeformer_visibility, 271 | "codeformer_weight": codeformer_weight, 272 | "upscaling_resize": upscaling_resize, 273 | "upscaling_resize_w": upscaling_resize_w, 274 | "upscaling_resize_h": upscaling_resize_h, 275 | "upscaling_crop": upscaling_crop, 276 | "upscaler_1": upscaler_1, 277 | "upscaler_2": upscaler_2, 278 | "extras_upscaler_2_visibility": extras_upscaler_2_visibility, 279 | "upscale_first": upscale_first, 280 | "image": b64_img(image), 281 | } 282 | 283 | return self.post_and_get_api_result( 284 | f"{self.baseurl}/extra-single-image", payload, use_async 285 | ) 286 | 287 | def extra_batch_images( 288 | self, 289 | images, # list of PIL images 290 | name_list=None, # list of image names 291 | resize_mode=0, 292 | show_extras_results=True, 293 | gfpgan_visibility=0, 294 | codeformer_visibility=0, 295 | codeformer_weight=0, 296 | upscaling_resize=2, 297 | upscaling_resize_w=512, 298 | upscaling_resize_h=512, 299 | upscaling_crop=True, 300 | upscaler_1="None", 301 | upscaler_2="None", 302 | extras_upscaler_2_visibility=0, 303 | upscale_first=False, 304 | use_async=False, 305 | ): 306 | if name_list is not None: 307 | if len(name_list) != len(images): 308 | raise RuntimeError("len(images) != len(name_list)") 309 | else: 310 | name_list = [f"image{i + 1:05}" for i in range(len(images))] 311 | images = [b64_img(x) for x in images] 312 | 313 | image_list = [] 314 | for name, image in zip(name_list, images): 315 | image_list.append({"data": image, "name": name}) 316 | 317 | payload = { 318 | "resize_mode": resize_mode, 319 | "show_extras_results": show_extras_results, 320 | "gfpgan_visibility": gfpgan_visibility, 321 | "codeformer_visibility": codeformer_visibility, 322 | "codeformer_weight": codeformer_weight, 323 | "upscaling_resize": upscaling_resize, 324 | "upscaling_resize_w": upscaling_resize_w, 325 | "upscaling_resize_h": upscaling_resize_h, 326 | "upscaling_crop": upscaling_crop, 327 | "upscaler_1": upscaler_1, 328 | "upscaler_2": upscaler_2, 329 | "extras_upscaler_2_visibility": extras_upscaler_2_visibility, 330 | "upscale_first": upscale_first, 331 | "imageList": image_list, 332 | } 333 | 334 | return self.post_and_get_api_result( 335 | f"{self.baseurl}/extra-batch-images", payload, use_async 336 | ) 337 | 338 | 339 | class EasyAI(EasyAPI): 340 | def __init__(self): 341 | super().__init__( 342 | host="127.0.0.1", 343 | port=80, 344 | use_https=False, 345 | ) 346 | 347 | 348 | easyai = EasyAI() 349 | -------------------------------------------------------------------------------- /easyai/result.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class APIResult: 6 | images: list 7 | parameters: dict 8 | info: dict 9 | 10 | @property 11 | def image(self): 12 | return self.images[0] 13 | -------------------------------------------------------------------------------- /easyai/upscaler.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Upscaler(str, Enum): 5 | none = "None" 6 | Lanczos = "Lanczos" 7 | Nearest = "Nearest" 8 | LDSR = "LDSR" 9 | BSRGAN = "BSRGAN" 10 | ESRGAN_4x = "ESRGAN_4x" 11 | R_ESRGAN_General_4xV3 = "R-ESRGAN General 4xV3" 12 | ScuNET_GAN = "ScuNET GAN" 13 | ScuNET_PSNR = "ScuNET PSNR" 14 | SwinIR_4x = "SwinIR 4x" 15 | 16 | 17 | class HiResUpscaler(str, Enum): 18 | none = "None" 19 | Latent = "Latent" 20 | LatentAntialiased = "Latent (antialiased)" 21 | LatentBicubic = "Latent (bicubic)" 22 | LatentBicubicAntialiased = "Latent (bicubic antialiased)" 23 | LatentNearest = "Latent (nearist)" 24 | LatentNearestExact = "Latent (nearist-exact)" 25 | Lanczos = "Lanczos" 26 | Nearest = "Nearest" 27 | ESRGAN_4x = "ESRGAN_4x" 28 | LDSR = "LDSR" 29 | ScuNET_GAN = "ScuNET GAN" 30 | ScuNET_PSNR = "ScuNET PSNR" 31 | SwinIR_4x = "SwinIR 4x" 32 | -------------------------------------------------------------------------------- /easyai_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "df7ba10f-5c88-4f8d-839d-b4b0fdad24a8", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# Setup API client" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "2e3e0414-8a0c-41bd-99e5-9b1a1a0bbe2a", 17 | "metadata": { 18 | "tags": [], 19 | "pycharm": { 20 | "is_executing": true 21 | } 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "!pip install easyai-sdwebui-api\n", 26 | "import easyai\n", 27 | "\n", 28 | "# create API client\n", 29 | "\n", 30 | "# create API client with custom host, port and https, default sampler, steps.\n", 31 | "api = easyai.EasyAPI(host='127.0.0.1', port=443, use_https=False, sampler='Euler a', steps=20)\n", 32 | "\n", 33 | "# optionally set username, password when --api-auth is set on webui.\n", 34 | "# api.set_auth('username', 'password')\n", 35 | "\n", 36 | "# check controlnet version\n", 37 | "api.controlnet_version()\n", 38 | "\n", 39 | "# list all controlnet models\n", 40 | "# api.controlnet_module_list()\n", 41 | "# api.controlnet_model_list()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "source": [], 47 | "metadata": { 48 | "collapsed": false 49 | } 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "id": "38134023-833e-4aba-ad97-c561c97a60a5", 54 | "metadata": {}, 55 | "source": [ 56 | "# Controlled Generative API Demo\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "id": "ddcea6a9-7914-445d-a4ee-bf03bca61e0e", 63 | "metadata": { 64 | "tags": [], 65 | "pycharm": { 66 | "is_executing": true 67 | } 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "# Generate a base image\n", 72 | "r = api.txt2img(\n", 73 | " prompt=\"a modern bathroom with purple walls\",\n", 74 | " negative_prompt=\"longbody, lowres, bad anatomy, extra digit, fewer digits, cropped, worst quality, low quality, out of frame\",\n", 75 | " height=512,\n", 76 | " seed=2023\n", 77 | ")\n", 78 | "\n", 79 | "# print(r.info)\n", 80 | "img = r.image\n", 81 | "img\n" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "id": "15cd06d2-b239-4e0e-8924-3c178b79ed37", 87 | "metadata": {}, 88 | "source": [ 89 | "# Style change" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "f1f7b1ad-3ab1-47fd-ad89-f78d066c19a3", 96 | "metadata": { 97 | "tags": [], 98 | "pycharm": { 99 | "is_executing": true 100 | } 101 | }, 102 | "outputs": [], 103 | "source": [ 104 | "unit1 = easyai.ControlNetUnit(input_image=img, module='canny', model='control_v11p_sd15_canny [d14c016b]')\n", 105 | "\n", 106 | "# style = \"modern\"\n", 107 | "# style = \"minimalist\"\n", 108 | "# style = \"professional\"\n", 109 | "# style = \"tropical\"\n", 110 | "# style = \"vintage\"\n", 111 | "# style = \"industrial\"\n", 112 | "# style = \"neoclassic\"\n", 113 | "style = \"Chinese\"\n", 114 | "\n", 115 | "\n", 116 | "r2 = api.img2img(\n", 117 | " prompt=f\"a {style} style bathroom, best quality, photo from Pinterest, interior, cinematic photo, ultra-detailed, ultra-realistic, award-winning, interior design, natural lighting\",\n", 118 | " images=[img],\n", 119 | " width=512,\n", 120 | " height=512,\n", 121 | " controlnet_units=[unit1],\n", 122 | " sampler_name=\"Euler a\",\n", 123 | " cfg_scale=7,\n", 124 | " seed=202304251052\n", 125 | ")\n", 126 | "\n", 127 | "# print(r2.info)\n", 128 | "new_img = r2.image\n", 129 | "new_img\n" 130 | ] 131 | } 132 | ], 133 | "metadata": { 134 | "kernelspec": { 135 | "display_name": "Python 3 (ipykernel)", 136 | "language": "python", 137 | "name": "python3" 138 | }, 139 | "language_info": { 140 | "codemirror_mode": { 141 | "name": "ipython", 142 | "version": 3 143 | }, 144 | "file_extension": ".py", 145 | "mimetype": "text/x-python", 146 | "name": "python", 147 | "nbconvert_exporter": "python", 148 | "pygments_lexer": "ipython3", 149 | "version": "3.10.10" 150 | } 151 | }, 152 | "nbformat": 4, 153 | "nbformat_minor": 5 154 | } 155 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [tool.flit.metadata] 6 | module = "easyai" 7 | dist-name = "easyai-sdwebui-api" 8 | author = "Freemind Core" 9 | author-email = "freemindcore@icloud.com" 10 | home-page = "https://github.com/freemindcore/sdwebuiapi" 11 | classifiers = [ 12 | "Intended Audience :: Information Technology", 13 | "Intended Audience :: System Administrators", 14 | "Operating System :: OS Independent", 15 | "Topic :: Internet", 16 | "Topic :: Software Development :: Libraries :: Application Frameworks", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | "Topic :: Software Development :: Libraries", 19 | "Topic :: Software Development", 20 | "Typing :: Typed", 21 | "Environment :: Web Environment", 22 | "Intended Audience :: Developers", 23 | "License :: OSI Approved :: MIT License", 24 | "Programming Language :: Python :: 3", 25 | "Programming Language :: Python :: 3.7", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | "Programming Language :: Python :: 3 :: Only", 31 | "Topic :: Internet :: WWW/HTTP :: HTTP Servers", 32 | "Topic :: Internet :: WWW/HTTP", 33 | ] 34 | 35 | requires = [ 36 | "requests", 37 | "Pillow", 38 | "aiohttp", 39 | ] 40 | description-file = "README.md" 41 | requires-python = ">=3.7" 42 | 43 | 44 | [tool.flit.metadata.urls] 45 | Documentation = "https://github.com/freemindcore/sdwebuiapi" 46 | 47 | [tool.flit.metadata.requires-extra] 48 | test = [ 49 | "pytest", 50 | "pytest-cov", 51 | "black", 52 | "isort", 53 | "flake8", 54 | "mypy==0.931", 55 | ] 56 | dev = [ 57 | "autoflake", 58 | "pre_commit", 59 | "bumpversion==0.6.0", 60 | ] 61 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | select = C,E,F,W,B,B950 4 | # it's not a bug that we aren't using all of hacking, ignore: 5 | # F812: list comprehension redefines ... 6 | # H101: Use TODO(NAME) 7 | # H202: assertRaises Exception too broad 8 | # H233: Python 3.x incompatible use of print operator 9 | # H301: one import per line 10 | # H306: imports not in alphabetical order (time, os) 11 | # H401: docstring should not start with a space 12 | # H403: multi line docstrings should end on a new line 13 | # H404: multi line docstring should start without a leading new line 14 | # H405: multi line docstring summary not separated with an empty line 15 | # H501: Do not use self.__dict__ for string formatting 16 | # W503: line break before binary operator 17 | # E231: missing whitespace after ',' 18 | ignore = D203,F812,H101,H202,H233,H301,H306,H401,H403,H404,H405,H501,W503,E231 19 | # C901: is too complex 20 | extend-ignore = 21 | E203, 22 | E501, 23 | C901 24 | per-file-ignores = 25 | # F401: imported but unused 26 | # F403: import * used; unable to detect undefined names 27 | __init__.py: F401,F403 28 | 29 | exclude = 30 | .tox, 31 | */migrations/*, 32 | */static/CACHE/*, 33 | docs, 34 | node_modules, 35 | venv, 36 | scripts/*, 37 | misc/local/*, 38 | # No need to traverse our git directory 39 | .git, 40 | # There's no value in checking cache directories 41 | __pycache__, 42 | # The conf file is mostly autogenerated, ignore it 43 | docs/source/conf.py, 44 | # The old directory contains Flake8 2.0 45 | old, 46 | # This contains our built documentation 47 | build, 48 | # This contains builds of flake8 that we don't want to check 49 | dist 50 | max-complexity = 10 51 | 52 | [pycodestyle] 53 | max-line-length = 88 54 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv 55 | 56 | [mypy] 57 | python_version = 3.11 58 | ignore_missing_imports = True 59 | warn_unused_configs = True 60 | plugins = mypy_django_plugin.main 61 | 62 | show_column_numbers = True 63 | 64 | follow_imports = normal 65 | 66 | # be strict 67 | disallow_untyped_calls = True 68 | warn_return_any = True 69 | strict_optional = True 70 | warn_no_return = True 71 | warn_redundant_casts = True 72 | warn_unused_ignores = True 73 | 74 | disallow_untyped_defs = True 75 | check_untyped_defs = True 76 | no_implicit_reexport = True 77 | 78 | [mypy-*.migrations.*] 79 | # Django migrations should not produce any errors: 80 | ignore_errors = True 81 | 82 | [mypy-tests.*] 83 | ignore_errors = True 84 | 85 | [coverage:run] 86 | include = easyai/* 87 | omit = *migrations*, *tests* 88 | --------------------------------------------------------------------------------