Skip to content

Commit 76086f6

Browse files
Merge branch 'master' into modal-save-button-and-shortcut
2 parents 403c5db + 02b5478 commit 76086f6

26 files changed

+2820
-889
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,3 @@ notification.mp3
2929
/textual_inversion
3030
.vscode
3131
/extensions
32-

javascript/hints.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ titles = {
7575
"Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style use that as placeholder for your prompt when you use the style in the future.",
7676

7777
"Checkpoint name": "Loads weights from checkpoint before making images. You can either use hash or a part of filename (as seen in settings) for checkpoint name. Recommended to use with Y axis for less switching.",
78+
"Inpainting conditioning mask strength": "Only applies to inpainting models. Determines how strongly to mask off the original image for inpainting and img2img. 1.0 means fully masked, which is the default behaviour. 0.0 means a fully unmasked conditioning. Lower values will help preserve the overall composition of the image, but will struggle with large changes.",
7879

7980
"vram": "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).",
8081

javascript/ui.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ function switch_to_txt2img(){
4545
return args_to_array(arguments);
4646
}
4747

48-
function switch_to_img2img_img2img(){
48+
function switch_to_img2img(){
4949
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
5050
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[0].click();
5151

5252
return args_to_array(arguments);
5353
}
5454

55-
function switch_to_img2img_inpaint(){
55+
function switch_to_inpaint(){
5656
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
5757
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[1].click();
5858

@@ -65,26 +65,6 @@ function switch_to_extras(){
6565
return args_to_array(arguments);
6666
}
6767

68-
function extract_image_from_gallery_txt2img(gallery){
69-
switch_to_txt2img()
70-
return extract_image_from_gallery(gallery);
71-
}
72-
73-
function extract_image_from_gallery_img2img(gallery){
74-
switch_to_img2img_img2img()
75-
return extract_image_from_gallery(gallery);
76-
}
77-
78-
function extract_image_from_gallery_inpaint(gallery){
79-
switch_to_img2img_inpaint()
80-
return extract_image_from_gallery(gallery);
81-
}
82-
83-
function extract_image_from_gallery_extras(gallery){
84-
switch_to_extras()
85-
return extract_image_from_gallery(gallery);
86-
}
87-
8868
function get_tab_index(tabId){
8969
var res = 0
9070

localizations/ar_AR.json

Lines changed: 389 additions & 421 deletions
Large diffs are not rendered by default.

localizations/de_DE.json

Lines changed: 419 additions & 0 deletions
Large diffs are not rendered by default.

localizations/it_IT.json

Lines changed: 492 additions & 0 deletions
Large diffs are not rendered by default.

localizations/pt_BR.json

Lines changed: 468 additions & 0 deletions
Large diffs are not rendered by default.

localizations/zh_TW.json

Lines changed: 488 additions & 0 deletions
Large diffs are not rendered by default.

modules/api/api.py

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,37 @@
1-
from modules.api.models import StableDiffusionTxt2ImgProcessingAPI, StableDiffusionImg2ImgProcessingAPI
1+
import uvicorn
2+
from gradio.processing_utils import encode_pil_to_base64, decode_base64_to_file, decode_base64_to_image
3+
from fastapi import APIRouter, HTTPException
4+
import modules.shared as shared
5+
from modules.api.models import *
26
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
37
from modules.sd_samplers import all_samplers
4-
from modules.extras import run_pnginfo
5-
import modules.shared as shared
6-
import uvicorn
7-
from fastapi import Body, APIRouter, HTTPException
8-
from fastapi.responses import JSONResponse
9-
from pydantic import BaseModel, Field, Json
10-
from typing import List
11-
import json
12-
import io
13-
import base64
14-
from PIL import Image
15-
16-
sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None)
8+
from modules.extras import run_extras
179

18-
class TextToImageResponse(BaseModel):
19-
images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
20-
parameters: Json
21-
info: Json
10+
def upscaler_to_index(name: str):
11+
try:
12+
return [x.name.lower() for x in shared.sd_upscalers].index(name.lower())
13+
except:
14+
raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be on of these: {' , '.join([x.name for x in sd_upscalers])}")
2215

23-
class ImageToImageResponse(BaseModel):
24-
images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
25-
parameters: Json
26-
info: Json
16+
sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None)
2717

18+
def setUpscalers(req: dict):
19+
reqDict = vars(req)
20+
reqDict['extras_upscaler_1'] = upscaler_to_index(req.upscaler_1)
21+
reqDict['extras_upscaler_2'] = upscaler_to_index(req.upscaler_2)
22+
reqDict.pop('upscaler_1')
23+
reqDict.pop('upscaler_2')
24+
return reqDict
2825

2926
class Api:
3027
def __init__(self, app, queue_lock):
3128
self.router = APIRouter()
3229
self.app = app
3330
self.queue_lock = queue_lock
34-
self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"])
35-
self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"])
36-
37-
def __base64_to_image(self, base64_string):
38-
# if has a comma, deal with prefix
39-
if "," in base64_string:
40-
base64_string = base64_string.split(",")[1]
41-
imgdata = base64.b64decode(base64_string)
42-
# convert base64 to PIL image
43-
return Image.open(io.BytesIO(imgdata))
31+
self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse)
32+
self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse)
33+
self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse)
34+
self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse)
4435

4536
def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI):
4637
sampler_index = sampler_to_index(txt2imgreq.sampler_index)
@@ -60,15 +51,9 @@ def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI):
6051
with self.queue_lock:
6152
processed = process_images(p)
6253

63-
b64images = []
64-
for i in processed.images:
65-
buffer = io.BytesIO()
66-
i.save(buffer, format="png")
67-
b64images.append(base64.b64encode(buffer.getvalue()))
68-
69-
return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=processed.js())
70-
54+
b64images = list(map(encode_pil_to_base64, processed.images))
7155

56+
return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js())
7257

7358
def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI):
7459
sampler_index = sampler_to_index(img2imgreq.sampler_index)
@@ -83,7 +68,7 @@ def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI):
8368

8469
mask = img2imgreq.mask
8570
if mask:
86-
mask = self.__base64_to_image(mask)
71+
mask = decode_base64_to_image(mask)
8772

8873

8974
populate = img2imgreq.copy(update={ # Override __init__ params
@@ -98,29 +83,48 @@ def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI):
9883

9984
imgs = []
10085
for img in init_images:
101-
img = self.__base64_to_image(img)
86+
img = decode_base64_to_image(img)
10287
imgs = [img] * p.batch_size
10388

10489
p.init_images = imgs
10590
# Override object param
10691
with self.queue_lock:
10792
processed = process_images(p)
10893

109-
b64images = []
110-
for i in processed.images:
111-
buffer = io.BytesIO()
112-
i.save(buffer, format="png")
113-
b64images.append(base64.b64encode(buffer.getvalue()))
94+
b64images = list(map(encode_pil_to_base64, processed.images))
11495

11596
if (not img2imgreq.include_init_images):
11697
img2imgreq.init_images = None
11798
img2imgreq.mask = None
99+
100+
return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.js())
118101

119-
return ImageToImageResponse(images=b64images, parameters=json.dumps(vars(img2imgreq)), info=processed.js())
102+
def extras_single_image_api(self, req: ExtrasSingleImageRequest):
103+
reqDict = setUpscalers(req)
120104

121-
def extrasapi(self):
122-
raise NotImplementedError
105+
reqDict['image'] = decode_base64_to_image(reqDict['image'])
106+
107+
with self.queue_lock:
108+
result = run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", **reqDict)
109+
110+
return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1])
111+
112+
def extras_batch_images_api(self, req: ExtrasBatchImagesRequest):
113+
reqDict = setUpscalers(req)
114+
115+
def prepareFiles(file):
116+
file = decode_base64_to_file(file.data, file_path=file.name)
117+
file.orig_name = file.name
118+
return file
119+
120+
reqDict['image_folder'] = list(map(prepareFiles, reqDict['imageList']))
121+
reqDict.pop('imageList')
122+
123+
with self.queue_lock:
124+
result = run_extras(extras_mode=1, image="", input_dir="", output_dir="", **reqDict)
123125

126+
return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1])
127+
124128
def pnginfoapi(self):
125129
raise NotImplementedError
126130

modules/api/models.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from array import array
2-
from inflection import underscore
3-
from typing import Any, Dict, Optional
1+
import inspect
42
from pydantic import BaseModel, Field, create_model
3+
from typing import Any, Optional
4+
from typing_extensions import Literal
5+
from inflection import underscore
56
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
6-
import inspect
7-
7+
from modules.shared import sd_upscalers
88

99
API_NOT_ALLOWED = [
1010
"self",
@@ -105,4 +105,47 @@ def generate_model(self):
105105
"StableDiffusionProcessingImg2Img",
106106
StableDiffusionProcessingImg2Img,
107107
[{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}]
108-
).generate_model()
108+
).generate_model()
109+
110+
class TextToImageResponse(BaseModel):
111+
images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
112+
parameters: dict
113+
info: str
114+
115+
class ImageToImageResponse(BaseModel):
116+
images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
117+
parameters: dict
118+
info: str
119+
120+
class ExtrasBaseRequest(BaseModel):
121+
resize_mode: Literal[0, 1] = Field(default=0, title="Resize Mode", description="Sets the resize mode: 0 to upscale by upscaling_resize amount, 1 to upscale up to upscaling_resize_h x upscaling_resize_w.")
122+
show_extras_results: bool = Field(default=True, title="Show results", description="Should the backend return the generated image?")
123+
gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.")
124+
codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.")
125+
codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.")
126+
upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=4, description="By how much to upscale the image, only used when resize_mode=0.")
127+
upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.")
128+
upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.")
129+
upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the choosen size?")
130+
upscaler_1: str = Field(default="None", title="Main upscaler", description=f"The name of the main upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}")
131+
upscaler_2: str = Field(default="None", title="Secondary upscaler", description=f"The name of the secondary upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}")
132+
extras_upscaler_2_visibility: float = Field(default=0, title="Secondary upscaler visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of secondary upscaler, values should be between 0 and 1.")
133+
134+
class ExtraBaseResponse(BaseModel):
135+
html_info: str = Field(title="HTML info", description="A series of HTML tags containing the process info.")
136+
137+
class ExtrasSingleImageRequest(ExtrasBaseRequest):
138+
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
139+
140+
class ExtrasSingleImageResponse(ExtraBaseResponse):
141+
image: str = Field(default=None, title="Image", description="The generated image in base64 format.")
142+
143+
class FileData(BaseModel):
144+
data: str = Field(title="File data", description="Base64 representation of the file")
145+
name: str = Field(title="File name")
146+
147+
class ExtrasBatchImagesRequest(ExtrasBaseRequest):
148+
imageList: list[FileData] = Field(title="Images", description="List of images to work on. Must be Base64 strings")
149+
150+
class ExtrasBatchImagesResponse(ExtraBaseResponse):
151+
images: list[str] = Field(title="Images", description="The generated images in base64 format.")

0 commit comments

Comments
 (0)