Move common settings code to settings_manager, refactor settings code to be more object oriented, decrease timeout back when request is successful, add interrogation tab, update custom models in README

main
natanjunges 2023-01-21 16:52:59 -03:00
parent ad0edc8283
commit 54a81d9d76
5 changed files with 263 additions and 96 deletions

View File

@ -3,9 +3,8 @@
## Features
- **txt2img**, **img2img**, **depth2img** and **inpaint**
- **txt2img**, **img2img**, **depth2img**, **inpaint** and **interrogation** (img2txt)
- *Outpaint coming soon*
- *Interrogate (img2txt) coming soon*
- **SFW and NSFW generations**
- **Share generated images with [LAION](https://laion.ai) for improving their dataset**
- *Aesthetic image rating coming soon*
@ -56,7 +55,7 @@
- **AIO Pixel Art** (v1): Stable Diffusion fine tuned on pixel art sprites and scenes
- **Analog Diffusion** (v1.0): A dreambooth model trained on a diverse set of analog photographs
- **Anygen** (v3.7): A best of both worlds - merging the anime of Anything v3 with Protogens photorealism - VAE is included
- **Anything Diffusion** (v3): Highly detailed Anime styled generations
- **Anything Diffusion** (v4.5): Highly detailed Anime styled generations
- **App Icon Diffusion** (v1): Dreambooth model fine tuned on mobile app icons
- **Arcane Diffusion** (v3): Based on the Arcane TV show
- **Archer Diffusion** (v1): Based on the Archer's TV show animation style
@ -69,6 +68,7 @@
- **Clazy** (v1): Generates clay-like figures
- **Comic-Diffusion** (v2): Western Comic book style
- **Cyberpunk Anime Diffusion** (v1): Cyberpunk anime characters
- **DGSpitzer Art Diffusion** (v1): Dreambooth model based on Vintedois, trained on a dataset of DGSpitzer art. Styles included are outline, sketch, anime, painting and landscape
- **Dark Victorian Diffusion** (v2.0): finetuned on dark, moody, victorian imagery
- **Darkest Diffusion** (v1.0): A free and open source Stable Diffusion model created by AI-Characters, trained on the artstyle of the game 'Darkest Dungeon'
- **DnD Item** (v1.0): This is a model (dnditem) for creating magic items, for the game Dungeons and Dragons! It was trained to be very similar to the official results that are available here: https://www.dndbeyond.com/magic-items
@ -76,6 +76,8 @@
- **DreamLikeSamKuvshinov** (v1): A mixture of Dreamlike Diffusion 1.0, SamDoesArt V3 and Kuvshinov style models. Created mostly for exploring different character concepts with a focus on drawings, but the mix happened to be pretty good at realistic-ish images, all thanks to wonderful models that it uses.
- **Dreamlike Diffusion** (v1.0): Dreamlike Diffusion 1.0 is SD 1.5 fine tuned on high quality art, made by dreamlike.art
- **Dreamlike Photoreal** (v2.0): Dreamlike Photoreal 1.0 is a photorealistic Stable Diffusion 1.5 model fine tuned on high quality photos, made by dreamlike.art.
- **Dreamshaper** (v3.3): Merged model mix of Midnight mixer, roboEtics, f222, elldrethSLucidMix, Seek.ART Mega, rpg, hassanBlend, modelshoot and roboDiffusion
- **DucHaiten** (v1.1): DucHaiten's character generation model
- **Dungeons and Diffusion** (v1): Generates D&D styled characters, trained on art commissions
- **Eimis Anime Diffusion** (v1): This model is trained with high quality and detailed anime images
- **Elden Ring Diffusion** (v2): Based on the Elden Ring video game style
@ -95,6 +97,8 @@
- **Inkpunk Diffusion** (v2): inspired by Gorillaz art, FLCL and Yoji Shinkawa. Trained on images generated from Midjourney
- **JWST Deep Space Diffusion** (v1): Stable Diffusion fine tuned on JWST imagery
- **Knollingcase** (v1): generates a glass display case with objects inside, inspired by Sean Preston. Trained on Midjourney images
- **Lawlas's yiff mix** (v1): Based on yiffy-e18 and Anything, produces sfw/nsfw furry anthro artworks of different styles with consistant quality, while maintaining details on stuff like clothes, background, etc. with simpler prompts.
- **Marvel Diffusion** (v2): This model was trained on images from the animated Marvel Disney+ show What If, which includes characters, background, and some objects
- **Mega Merge Diffusion** (v1): SD 1.5 merged with 17 other models
- **Microscopic** (v1.0): This is the fine-tuned Stable Diffusion model trained on microscopic images
- **Microworlds** (v1): Isometric microworlds
@ -111,6 +115,7 @@
- **Poison** (v1): Anything Diffusion fine-tuned to produce high-quality realistic anime styled images
- **PortraitPlus** (v1.0): This is a dreambooth model trained on a diverse set of close to medium range portraits of people.
- **ProtoGen** (v5.3): One Step Closer to Reality
- **Protogen Infinity** (v8.6): Protogens photorealism mixed with more science fiction, comic, and synthwave to make ultimate awesomeness
- **RPG** (v2): portraits of charecters in the style of the game Baldur's Gate
- **Ranma Diffusion** (v1): imitates the style of late '80s early 90's anime, Anything v3 base
- **Redshift Diffusion** (v1): Dreambooth model trained on high resolution 3D artworks
@ -123,8 +128,9 @@
- **Spider-Verse Diffusion** (v1): Based on the Into the Spider-Verse movie's animation style
- **Squishmallow Diffusion** (v1): Squishmallows
- **Supermarionation** (v2.0): This is a fine-tuned Stable Diffusion model (based on v1.5) trained on screenshots from Gerry Anderson Supermarionation stop motion animation movie, basically from Thunderbirds tv series
- **Sygil-Dev Diffusion** (v1): This model is a Stable Diffusion v1.5 fine-tune trained on the Imaginary Network Expanded Dataset. It is an advanced version of Stable Diffusion and can generate nearly all kinds of images, no matter humans, reflections, cities, architecture, fantasy, digital arts, landscapes, or nature views.
- **Sygil-Dev Diffusion** (v0.1): This model is a Stable Diffusion v1.5 fine-tune trained on the Imaginary Network Expanded Dataset. It is an advanced version of Stable Diffusion and can generate nearly all kinds of images, no matter humans, reflections, cities, architecture, fantasy, digital arts, landscapes, or nature views.
- **Synthwave** (v1): Stable Diffusion model to create images in Synthwave/outrun style
- **T-Shirt Diffusion** (v1): Generates t-shirt logos, base model is vintedois-diffusion with additional training on t-shirt logos size 640x640px
- **Trinart Characters** (v2.0): Derrida (formerly TrinArt Characters v2) is a stable diffusion v1-based model that was further improved on the previous characters v1 model. While this is still a versatility and compositional variation anime/manga model like other TrinArt models, when compared to the v1 model, Derrida was focused on more anatomical stability and slightly less on variation due to further multi-epoch training and finetuning.
- **Tron Legacy Diffusion** (v1): Tron Legacy movie style
- **Ultraskin** (v0.9): This model will add a LOT of skin detail compared to SD 2.1. Sometimes this makes images look more realistic, sometimes less realistic!
@ -137,6 +143,7 @@
- **Yiffy** (v18): Furry styled generations.
- **Zack3D** (v1): Kink/NSFW oriented furry styled generations.
- **Zeipher Female Model** (v222): For creating images of nude solo women. Also known as f222
- **Zelda BOTW** (v1): based off work of great artworks from Legend of Zelda: Breath of The Wild
- **colorbook** (v1): Minimalist coloring book style images
- **kurzgesagt** (v1): A DreamBooth finetune of Stable Diffusion v1.5 model trained on a bunch of stills from Kurzgesagt videos
- **mo-di-diffusion** (v1): Popular animation studio modern style generations.

146
scripts/interrogation.py Normal file
View File

@ -0,0 +1,146 @@
# Stable Horde for Web UI, a Stable Horde client for AUTOMATIC1111's Stable Diffusion Web UI
# Copyright (C) 2022 Natan Junges <natanajunges@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from modules import script_callbacks
import gradio
import requests
import time
from scripts.settings_manager import SettingsManager
class StableHordeInterrogateError(Exception):
pass
class Interrogation(SettingsManager):
def __init__(self):
self.interrupted = False
def ui(self):
with gradio.Blocks(analytics_enabled=False) as interrogation_tab:
source_image = gradio.Textbox(max_lines=1, label="Source image URL", interactive=True)
nsfw = gradio.Checkbox(label="NSFW: Returns a true/false boolean depending on whether the image is displaying NSFW imagery or not", interactive=True)
caption = gradio.Checkbox(label="Caption: Returns a string describing the image", interactive=True)
interrogation = gradio.Checkbox(label="Interrogation: Returns a dictionary of key words best describing the image, with an accompanying confidence score", interactive=True)
with gradio.Row():
cancel = gradio.Button(value="Cancel")
interrogate = gradio.Button(value="Interrogate", variant="primary")
results = gradio.JSON(label="Results")
def cancel_click():
self.interrupted = True
def interrogate_click(source_image, nsfw, caption, interrogation):
self.interrupted = False
try:
ret = self.run(source_image, nsfw, caption, interrogation)
except Exception as e:
ret = {e.__class__.__name__: str(e)}
return (gradio.update(value=ret))
cancel.click(fn=cancel_click)
interrogate.click(fn=interrogate_click, inputs=[source_image, nsfw, caption, interrogation], outputs=results, scroll_to_output=True)
return [(interrogation_tab, "Stable Horde Interrogation", "stable_horde_interrogation")]
def run(self, source_image, nsfw, caption, interrogation):
if len(source_image) == 0:
raise StableHordeInterrogateError("Source image URL must be provided")
payload = {
"source_image": source_image,
"forms": []
}
self.load_settings()
if nsfw:
payload["forms"].append({
"name": "nsfw"
})
if caption:
payload["forms"].append({
"name": "caption"
})
if interrogation:
payload["forms"].append({
"name": "interrogation"
})
if len(payload["forms"]) == 0:
raise StableHordeInterrogateError("At least one option must be chosen")
if self.interrupted:
return
try:
session = requests.Session()
id = session.post("{}/v2/interrogate/async".format(self.api_endpoint), headers={"apikey": self.api_key}, json=payload)
assert id.status_code == 202, "Status Code: {} (expected {})".format(id.status_code, 202)
id = id.json()
id = id["id"]
timeout = 1
while True:
if self.interrupted:
return self.cancel_interrogate(id)
try:
status = session.get("{}/v2/interrogate/status/{}".format(self.api_endpoint, id), timeout=timeout)
assert status.status_code == 200, "Status Code: {} (expected {})".format(status.status_code, 200)
status = status.json()
if status["state"] == "done":
results = {}
for form in status["forms"]:
results.update(form["result"])
return results
else:
if timeout > 1:
timeout //= 2
time.sleep(1)
except requests.Timeout:
if timeout >= 60:
raise StableHordeInterrogateError("Reached maximum number of retries")
timeout *= 2
time.sleep(1)
except AssertionError:
status = status.json()
raise StableHordeInterrogateError(status["message"])
except AssertionError:
id = id.json()
raise StableHordeInterrogateError(id["message"])
def cancel_interrogate(self, id):
status = requests.delete("{}/v2/interrogate/status/{}".format(self.api_endpoint, id), timeout=60)
status = status.json()
results = {}
for form in status["forms"]:
if form["state"] == "done":
results.update(form["result"])
if len(results) > 0:
return results
script_callbacks.on_ui_tabs(Interrogation().ui)

View File

@ -25,9 +25,7 @@ import os.path
import numpy
import itertools
import torch
import json
settings_file = os.path.join(scripts.basedir(), "settings.json")
from scripts.settings_manager import SettingsManager
class FakeCheckpointInfo:
def __init__(self, model_name):
@ -39,10 +37,10 @@ class FakeModel:
def __init__(self, name):
self.sd_checkpoint_info = FakeCheckpointInfo(name)
class StableHordeError(Exception):
class StableHordeGenerateError(Exception):
pass
class Main(scripts.Script):
class Main(SettingsManager, scripts.Script):
TITLE = "Run on Stable Horde"
SAMPLERS = {
"LMS": "k_lms",
@ -70,23 +68,6 @@ class Main(scripts.Script):
def show(self, is_img2img):
return True
def load_settings(self):
if os.path.exists(settings_file):
with open(settings_file) as file:
opts = json.load(file)
self.api_endpoint = opts["api_endpoint"]
self.api_key = opts["api_key"]
self.censor_nsfw = opts["censor_nsfw"]
self.trusted_workers = opts["trusted_workers"]
self.workers = opts["workers"]
else:
self.api_endpoint = "https://stablehorde.net/api"
self.api_key = "0000000000"
self.censor_nsfw = True
self.trusted_workers = True
self.workers = []
def load_models(self):
self.load_settings()
@ -465,23 +446,26 @@ class Main(scripts.Script):
images = [torch.from_numpy(image) for image in images]
return (images, models)
elif status["faulted"]:
raise StableHordeError("This request caused an internal server error and could not be completed.")
raise StableHordeGenerateError("This request caused an internal server error and could not be completed.")
elif not status["is_possible"]:
raise StableHordeError("This request will not be able to be completed with the pool of workers currently available.")
raise StableHordeGenerateError("This request will not be able to be completed with the pool of workers currently available.")
else:
if timeout > 1:
timeout //= 2
time.sleep(1)
except requests.Timeout:
if timeout >= 60:
raise StableHordeError("Reached maximum number of retries")
raise StableHordeGenerateError("Reached maximum number of retries")
timeout *= 2
time.sleep(1)
except AssertionError:
status = status.json()
raise StableHordeError(status["message"])
raise StableHordeGenerateError(status["message"])
except AssertionError:
id = id.json()
raise StableHordeError(id["message"])
raise StableHordeGenerateError(id["message"])
def cancel_process_batch_horde(self, id):
images = requests.delete("{}/v2/generate/status/{}".format(self.api_endpoint, id), timeout=60)

View File

@ -14,82 +14,58 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from modules import script_callbacks, scripts, ui
from modules import script_callbacks, ui
import gradio
import json
import os.path
from scripts.settings_manager import SettingsManager
settings_file = os.path.join(scripts.basedir(), "settings.json")
class Settings(SettingsManager):
def ui(self):
with gradio.Blocks(analytics_enabled=False) as settings_tab:
api_endpoint = gradio.Textbox(max_lines=1, placeholder="https://stablehorde.net/api", label="API endpoint", interactive=True)
def settings():
with gradio.Blocks(analytics_enabled=False) as settings_tab:
api_endpoint = gradio.Textbox(max_lines=1, placeholder="https://stablehorde.net/api", label="API endpoint", interactive=True)
with gradio.Row():
api_key = gradio.Textbox(max_lines=1, placeholder="0000000000", label="API key", interactive=True, type="password")
show = gradio.Button(value="Show", elem_id="horde_show_api_key")
with gradio.Row():
api_key = gradio.Textbox(max_lines=1, placeholder="0000000000", label="API key", interactive=True, type="password")
show = gradio.Button(value="Show", elem_id="horde_show_api_key")
censor_nsfw = gradio.Checkbox(label="Censor NSFW when NSFW is disabled", interactive=True)
trusted_workers = gradio.Checkbox(label="Only send requests to trusted workers", interactive=True)
workers = gradio.Textbox(max_lines=1, label="Only send requests to these workers", interactive=True)
censor_nsfw = gradio.Checkbox(label="Censor NSFW when NSFW is disabled", interactive=True)
trusted_workers = gradio.Checkbox(label="Only send requests to trusted workers", interactive=True)
workers = gradio.Textbox(max_lines=1, label="Only send requests to these workers", interactive=True)
with gradio.Row():
reset = gradio.Button(value=ui.reuse_symbol + " Reset settings")
reload = gradio.Button(value=ui.refresh_symbol + " Reload settings")
apply = gradio.Button(value=ui.save_style_symbol + " Apply settings", variant="primary")
with gradio.Row():
reset = gradio.Button(value=ui.reuse_symbol + " Reset settings")
reload = gradio.Button(value=ui.refresh_symbol + " Reload settings")
apply = gradio.Button(value=ui.save_style_symbol + " Apply settings", variant="primary")
def show_click(show):
return (gradio.update(type="text" if show == "Show" else "password"), gradio.update(value="Hide" if show == "Show" else "Show"))
def show_click(show):
return (gradio.update(type="text" if show == "Show" else "password"), gradio.update(value="Hide" if show == "Show" else "Show"))
def reset_click():
self.reset_settings()
return (gradio.update(value=self.api_endpoint), gradio.update(value=self.api_key), gradio.update(value=self.censor_nsfw), gradio.update(value=self.trusted_workers), gradio.update(value=", ".join(self.workers)))
def reset_click():
opts = {
"api_endpoint": "https://stablehorde.net/api",
"api_key": "0000000000",
"censor_nsfw": True,
"trusted_workers": True,
"workers": []
}
def reload_click():
self.load_settings()
return (gradio.update(value=self.api_endpoint), gradio.update(value=self.api_key), gradio.update(value=self.censor_nsfw), gradio.update(value=self.trusted_workers), gradio.update(value=", ".join(self.workers)))
return (gradio.update(value=opts["api_endpoint"]), gradio.update(value=opts["api_key"]), gradio.update(value=opts["censor_nsfw"]), gradio.update(value=opts["trusted_workers"]), gradio.update(value=", ".join(opts["workers"])))
def apply_click(api_endpoint, api_key, censor_nsfw, trusted_workers, workers):
self.api_endpoint = api_endpoint if len(api_endpoint) > 0 else "https://stablehorde.net/api"
self.api_key = api_key if len(api_key) > 0 else "0000000000"
self.censor_nsfw = censor_nsfw
self.trusted_workers = trusted_workers
def reload_click():
if os.path.exists(settings_file):
with open(settings_file) as file:
opts = json.load(file)
if len(workers) == 0:
self.workers = []
else:
self.workers = list(map(lambda w: w.strip(), workers.split(",")))
return (gradio.update(value=opts["api_endpoint"]), gradio.update(value=opts["api_key"]), gradio.update(value=opts["censor_nsfw"]), gradio.update(value=opts["trusted_workers"]), gradio.update(value=", ".join(opts["workers"])))
else:
return reset_click()
self.save_settings()
def apply_click(api_endpoint, api_key, censor_nsfw, trusted_workers, workers):
if len(api_endpoint) == 0:
api_endpoint = "https://stablehorde.net/api"
show.click(fn=show_click, inputs=show, outputs=[api_key, show])
reset.click(fn=reset_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
reload.click(fn=reload_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
apply.click(fn=apply_click, inputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
settings_tab.load(fn=reload_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
if len(api_key) == 0:
api_key = "0000000000"
return [(settings_tab, "Stable Horde Settings", "stable_horde_settings")]
if len(workers) == 0:
workers = []
else:
workers = list(map(lambda w: w.strip(), workers.split(",")))
opts = {
"api_endpoint": api_endpoint,
"api_key": api_key,
"censor_nsfw": censor_nsfw,
"trusted_workers": trusted_workers,
"workers": workers
}
with open(settings_file, "w") as file:
json.dump(opts, file)
show.click(fn=show_click, inputs=show, outputs=[api_key, show])
reset.click(fn=reset_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
reload.click(fn=reload_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
apply.click(fn=apply_click, inputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
settings_tab.load(fn=reload_click, outputs=[api_endpoint, api_key, censor_nsfw, trusted_workers, workers])
return [(settings_tab, "Stable Horde Settings", "stable_horde_settings")]
script_callbacks.on_ui_tabs(settings)
script_callbacks.on_ui_tabs(Settings().ui)

View File

@ -0,0 +1,54 @@
# Stable Horde for Web UI, a Stable Horde client for AUTOMATIC1111's Stable Diffusion Web UI
# Copyright (C) 2022 Natan Junges <natanajunges@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from modules import scripts
import json
import os.path
class SettingsManager:
settings_file = os.path.join(scripts.basedir(), "settings.json")
def reset_settings(self):
self.api_endpoint = "https://stablehorde.net/api"
self.api_key = "0000000000"
self.censor_nsfw = True
self.trusted_workers = True
self.workers = []
def load_settings(self):
if os.path.exists(self.settings_file):
with open(self.settings_file) as file:
opts = json.load(file)
self.api_endpoint = opts["api_endpoint"]
self.api_key = opts["api_key"]
self.censor_nsfw = opts["censor_nsfw"]
self.trusted_workers = opts["trusted_workers"]
self.workers = opts["workers"]
else:
self.reset_settings()
def save_settings(self):
opts = {
"api_endpoint": self.api_endpoint,
"api_key": self.api_key,
"censor_nsfw": self.censor_nsfw,
"trusted_workers": self.trusted_workers,
"workers": self.workers
}
with open(self.settings_file, "w") as file:
json.dump(opts, file)