Commit cabd4e3b authored by Muhammad Rizqi Nur's avatar Muhammad Rizqi Nur

Merge branch 'master' into gradient-clipping

parents bb832d77 804d9fb8
* @AUTOMATIC1111
/localizations/ar_AR.json @xmodar @blackneoo
/localizations/de_DE.json @LunixWasTaken
/localizations/es_ES.json @innovaciones
/localizations/fr_FR.json @tumbly
/localizations/it_IT.json @EugenioBuffo
/localizations/ja_JP.json @yuuki76
/localizations/ko_KR.json @36DB
/localizations/pt_BR.json @M-art-ucci
/localizations/ru_RU.json @kabachuha
/localizations/tr_TR.json @camenduru
/localizations/zh_CN.json @dtlnor @bgluminous
/localizations/zh_TW.json @benlisquare
# if you were managing a localization and were removed from this file, this is because
# the intended way to do localizations now is via extensions. See:
# https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions
# Make a repo with your localization and since you are still listed as a collaborator
# you can add it to the wiki page yourself. This change is because some people complained
# the git commit log is cluttered with things unrelated to almost everyone and
# because I believe this is the best overall for the project to handle localizations almost
# entirely without my oversight.
......@@ -208,4 +208,6 @@ function update_token_counter(button_id) {
function restart_reload(){
document.body.innerHTML='<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>';
setTimeout(function(){location.reload()},2000)
return []
}
......@@ -142,7 +142,7 @@ def prepare_enviroment():
stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc")
taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6")
k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "f4e99857772fc3a126ba886aadf795a332774878")
k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "60e5042ca0da89c14d1dd59d73883280f8fce991")
codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af")
blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
......
This diff is collapsed.
......@@ -16,6 +16,7 @@
"A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의",
"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.",
"Action": "작업",
"Add a button to convert the prompts used in NovelAI for use in the WebUI. In addition, add a button that allows you to recall a previously used prompt.": "NovelAI에서 사용되는 프롬프트를 WebUI에서 사용할 수 있게 변환하는 버튼을 추가합니다. 덤으로 이전에 사용한 프롬프트를 불러오는 버튼도 추가됩니다.",
"Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가",
"Add a second progress bar to the console that shows progress for an entire job.": "콘솔에 전체 작업의 진행도를 보여주는 2번째 프로그레스 바 추가하기",
"Add difference": "차이점 추가",
......@@ -24,6 +25,7 @@
"Add model hash to generation information": "생성 정보에 모델 해시 추가",
"Add model name to generation information": "생성 정보에 모델 이름 추가",
"Add number to filename when saving": "이미지를 저장할 때 파일명에 숫자 추가하기",
"Adds a tab that lets you preview how CLIP model would tokenize your text.": "CLIP 모델이 텍스트를 어떻게 토큰화할지 미리 보여주는 탭을 추가합니다.",
"Adds a tab to the webui that allows the user to automatically extract keyframes from video, and manually extract 512x512 crops of those frames for use in model training.": "WebUI에 비디오로부터 자동으로 키프레임을 추출하고, 그 키프레임으로부터 모델 훈련에 사용될 512x512 이미지를 잘라낼 수 있는 탭을 추가합니다.",
"Aesthetic Gradients": "스타일 그라디언트",
"Aesthetic Image Scorer": "스타일 이미지 스코어러",
......@@ -33,6 +35,7 @@
"Aesthetic text for imgs": "스타일 텍스트",
"Aesthetic weight": "스타일 가중치",
"Allowed categories for random artists selection when using the Roll button": "랜덤 버튼을 눌러 무작위 작가를 선택할 때 허용된 카테고리",
"Allows you to include various shortcodes in your prompts. You can pull text from files, set up your own variables, process text through conditional functions, and so much more - it's like wildcards on steroids.": "프롬프트에 다양한 숏코드를 추가할 수 있게 해줍니다. 파일로부터 텍스트 추출, 변수 설정, 조건 함수로 텍스트 처리 등등 - 스테로이드를 맞은 와일드카드라 할 수 있죠.",
"Always print all generation info to standard output": "기본 아웃풋에 모든 생성 정보 항상 출력하기",
"Always save all generated image grids": "생성된 이미지 그리드 항상 저장하기",
"Always save all generated images": "생성된 이미지 항상 저장하기",
......@@ -54,6 +57,7 @@
"Batch Process": "이미지 여러장 처리",
"Batch size": "배치 크기",
"behind": "최신 아님",
"Booru tag autocompletion": "Booru 태그 자동완성",
"BSRGAN 4x": "BSRGAN 4x",
"built with gradio": "gradio로 제작되었습니다",
"Calculates aesthetic score for generated images using CLIP+MLP Aesthetic Score Predictor based on Chad Scorer": "Chad 스코어러를 기반으로 한 CLIP+MLP 스타일 점수 예측기를 이용해 생성된 이미지의 스타일 점수를 계산합니다.",
......@@ -114,6 +118,7 @@
"Directory for saving images using the Save button": "저장 버튼을 이용해 저장하는 이미지들의 저장 경로",
"Directory name pattern": "디렉토리명 패턴",
"directory.": "저장 경로에 저장됩니다.",
"Displays autocompletion hints for tags from image booru boards such as Danbooru. Uses local tag CSV files and includes a config for customization.": "Danbooru 같은 이미지 booru 보드의 태그에 대한 자동완성 힌트를 보여줍니다. 로컬 환경에 저장된 CSV 파일을 사용하고 조정 가능한 설정 파일이 포함되어 있습니다.",
"Do not add watermark to images": "이미지에 워터마크 추가하지 않기",
"Do not do anything special": "아무것도 하지 않기",
"Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기",
......@@ -317,6 +322,7 @@
"None": "없음",
"Nothing": "없음",
"Nothing found in the image.": "Nothing found in the image.",
"novelai-2-local-prompt": "NovelAI 프롬프트 변환기",
"Number of columns on the page": "각 페이지마다 표시할 가로줄 수",
"Number of grids in each row": "각 세로줄마다 표시될 그리드 수",
"number of images to delete consecutively next": "연속적으로 삭제할 이미지 수",
......@@ -431,6 +437,7 @@
"Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장",
"Save style": "스타일 저장",
"Save text information about generation parameters as chunks to png files": "이미지 생성 설정값을 PNG 청크에 텍스트로 저장",
"Saves Optimizer state as separate *.optim file. Training can be resumed with HN itself and matching optim file.": "옵티마이저 상태를 별개의 *.optim 파일로 저장하기. 하이퍼네트워크 파일과 일치하는 optim 파일로부터 훈련을 재개할 수 있습니다.",
"Saving images/grids": "이미지/그리드 저장",
"Saving to a directory": "디렉토리에 저장",
"Scale by": "스케일링 배수 지정",
......@@ -515,6 +522,7 @@
"Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGAN 업스케일러들의 타일 사이즈. 0 = 타일링 없음.",
"Tiling": "타일링",
"Time taken:": "소요 시간 : ",
"tokenizer": "토크나이저",
"Torch active/reserved:": "활성화/예약된 Torch 양 : ",
"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%).": "활성화된 Torch : 생성 도중 캐시된 데이터를 포함해 사용된 VRAM의 최대량\n예약된 Torch : 활성화되고 캐시된 모든 데이터를 포함해 Torch에게 할당된 VRAM의 최대량\n시스템 VRAM : 모든 어플리케이션에 할당된 VRAM 최대량 / 총 GPU VRAM (최고 이용도%)",
"Train": "훈련",
......
......@@ -109,7 +109,7 @@
"Sigma noise": "Sigma noise",
"Eta": "Eta",
"Clip skip": "Clip 跳过",
"Denoising": "去噪",
"Denoising": "重绘幅度",
"Cond. Image Mask Weight": "图像调节屏蔽度",
"X values": "X轴数值",
"Y type": "Y轴类型",
......
......@@ -7,7 +7,7 @@
"Loading...": "載入中…",
"view": "檢視",
"api": "api",
"•": "",
"•": "",
"built with gradio": "基於 Gradio 構建",
"Stable Diffusion checkpoint": "Stable Diffusion 模型權重存檔點",
"txt2img": "文生圖",
......@@ -70,12 +70,12 @@
"Variation strength": "差異強度",
"Resize seed from width": "自寬度縮放隨機種子",
"Resize seed from height": "自高度縮放隨機種子",
"Open for Clip Aesthetic!": "打開美術風格 Clip!",
"Open for Clip Aesthetic!": "打開以調整 Clip 的美術風格!",
"▼": "▼",
"Aesthetic weight": "美術風格權重",
"Aesthetic steps": "美術風格疊代步數",
"Aesthetic learning rate": "美術風格學習率",
"Slerp interpolation": "Slerp 插值",
"Slerp interpolation": "球面線性插值角度",
"Aesthetic imgs embedding": "美術風格圖集 embedding",
"None": "無",
"Aesthetic text for imgs": "該圖集的美術風格描述",
......@@ -105,15 +105,15 @@
"Prompt order": "提示詞順序",
"Sampler": "採樣器",
"Checkpoint name": "模型權重存檔點的名稱",
"Hypernetwork": "超網路",
"Hypernet str.": "超網路強度",
"Hypernetwork": "超網路(Hypernetwork)",
"Hypernet str.": "超網路(Hypernetwork)強度",
"Sigma Churn": "Sigma Churn",
"Sigma min": "最小 Sigma",
"Sigma max": "最大 Sigma",
"Sigma noise": "Sigma noise",
"Eta": "Eta",
"Clip skip": "Clip 跳過",
"Denoising": "去噪",
"Denoising": "重繪幅度",
"Cond. Image Mask Weight": "圖像調節屏蔽度",
"X values": "X軸數值",
"Y type": "Y軸類型",
......@@ -189,6 +189,7 @@
"Tile overlap": "圖塊重疊的畫素",
"Upscaler": "放大演算法",
"Lanczos": "Lanczos",
"Nearest": "最鄰近(整數縮放)",
"LDSR": "LDSR",
"BSRGAN 4x": "BSRGAN 4x",
"ESRGAN_4x": "ESRGAN_4x",
......@@ -230,15 +231,15 @@
"for detailed explanation.": "以了解詳細說明",
"Create embedding": "生成 embedding",
"Create aesthetic images embedding": "生成美術風格圖集 embedding",
"Create hypernetwork": "生成 hypernetwork",
"Create hypernetwork": "生成超網路(Hypernetwork)",
"Preprocess images": "圖像預處理",
"Name": "名稱",
"Initialization text": "初始化文字",
"Number of vectors per token": "每個 token 的向量數",
"Overwrite Old Embedding": "覆寫舊的 Embedding",
"Modules": "模組",
"Enter hypernetwork layer structure": "輸入 hypernetwork 層結構",
"Select activation function of hypernetwork": "選擇 hypernetwork 的激活函數",
"Enter hypernetwork layer structure": "輸入超網路(Hypernetwork)層結構",
"Select activation function of hypernetwork": "選擇超網路(Hypernetwork)的激活函數",
"linear": "linear",
"relu": "relu",
"leakyrelu": "leakyrelu",
......@@ -276,7 +277,7 @@
"XavierNormal": "Xavier 正態",
"Add layer normalization": "加入層標準化",
"Use dropout": "採用 dropout 防止過擬合",
"Overwrite Old Hypernetwork": "覆寫舊的 Hypernetwork",
"Overwrite Old Hypernetwork": "覆寫舊的超網路(Hypernetwork)",
"Source directory": "來源目錄",
"Destination directory": "目標目錄",
"Existing Caption txt Action": "對已有的TXT說明文字的行為",
......@@ -298,11 +299,11 @@
"Create debug image": "生成除錯圖片",
"Preprocess": "預處理",
"Train an embedding; must specify a directory with a set of 1:1 ratio images": "訓練 embedding; 必須指定一組具有 1:1 比例圖像的目錄",
"Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "訓練 embedding 或者 hypernetwork; 必須指定一組具有 1:1 比例圖像的目錄",
"[wiki]": "[wiki]",
"Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "訓練 embedding 或者超網路(Hypernetwork); 必須指定一組具有 1:1 比例圖像的目錄",
"[wiki]": "[wiki文件]",
"Embedding": "Embedding",
"Embedding Learning rate": "Embedding 學習率",
"Hypernetwork Learning rate": "Hypernetwork 學習率",
"Hypernetwork Learning rate": "超網路(Hypernetwork)學習率",
"Learning rate": "學習率",
"Dataset directory": "資料集目錄",
"Log directory": "日誌目錄",
......@@ -312,7 +313,7 @@
"Save a copy of embedding to log directory every N steps, 0 to disable": "每 N 步將 embedding 的副本儲存到日誌目錄,0 表示禁用",
"Save images with embedding in PNG chunks": "儲存圖像,並在 PNG 圖片檔案中嵌入 embedding 檔案",
"Read parameters (prompt, etc...) from txt2img tab when making previews": "進行預覽時,從文生圖頁籤中讀取參數(提示詞等)",
"Train Hypernetwork": "訓練 Hypernetwork",
"Train Hypernetwork": "訓練超網路(Hypernetwork)",
"Train Embedding": "訓練 Embedding",
"Create an aesthetic embedding out of any number of images": "從任意數量的圖像中建立美術風格 embedding",
"Create images embedding": "生成圖集 embedding",
......@@ -418,7 +419,7 @@
"Checkpoints to cache in RAM": "快取在內存(RAM)中的模型權重存檔點",
"SD VAE": "模型的VAE",
"auto": "自動",
"Hypernetwork strength": "Hypernetwork 強度",
"Hypernetwork strength": "超網路(Hypernetwork)強度",
"Inpainting conditioning mask strength": "局部重繪時圖像調節的蒙版屏蔽強度",
"Apply color correction to img2img results to match original colors.": "對圖生圖結果套用顏色校正以匹配原始顏色",
"With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "在進行圖生圖的時候,確切地執行滑塊指定的疊代步數(正常情況下更弱的重繪幅度需要更少的疊代步數)",
......@@ -488,7 +489,17 @@
"Extension": "擴充",
"URL": "網址",
"Update": "更新",
"a1111-sd-webui-tagcomplete": "標記自動補全",
"unknown": "未知",
"deforum-for-automatic1111-webui": "Deforum",
"sd-dynamic-prompting": "動態提示詞",
"stable-diffusion-webui-aesthetic-gradients": "美術風格梯度",
"stable-diffusion-webui-aesthetic-image-scorer": "美術風格評等",
"stable-diffusion-webui-artists-to-study": "藝術家圖庫",
"stable-diffusion-webui-dataset-tag-editor": "資料集標記編輯器",
"stable-diffusion-webui-images-browser": "圖庫瀏覽器",
"stable-diffusion-webui-inspiration": "靈感",
"stable-diffusion-webui-wildcards": "萬用字元",
"Load from:": "載入自",
"Extension index URL": "擴充清單連結",
"URL for extension's git repository": "擴充的 git 倉庫連結",
......@@ -527,8 +538,8 @@
"What to put inside the masked area before processing it with Stable Diffusion.": "在使用 Stable Diffusion 處理蒙版區域之前要在蒙版區域內放置什麼",
"fill it with colors of the image": "用圖像的顏色(高強度模糊)填充它",
"keep whatever was there originally": "保留原來的圖像,不進行預處理",
"fill it with latent space noise": "用潛空間的噪聲填充它",
"fill it with latent space zeroes": "用潛空間的零填充它",
"fill it with latent space noise": "於潛空間填充噪聲",
"fill it with latent space zeroes": "於潛空間填零",
"Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "將蒙版區域(包括預留畫素長度的緩衝區域)放大到目標解析度,進行局部重繪。\n然後縮小並粘貼回原始圖像中",
"Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "將圖像大小調整為目標解析度。除非高度和寬度匹配,否則你將獲得不正確的縱橫比",
"Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "調整圖像大小,使整個目標解析度都被圖像填充。裁剪多出來的部分",
......@@ -560,6 +571,8 @@
"Select which Real-ESRGAN models to show in the web UI. (Requires restart)": "選擇哪些 Real-ESRGAN 模型顯示在網頁使用者介面。(需要重新啟動)",
"Allowed categories for random artists selection when using the Roll button": "使用抽選藝術家按鈕時將會隨機的藝術家類別",
"Append commas": "附加逗號",
"latest": "最新",
"behind": "落後",
"Roll three": "抽三位出來",
"Generate forever": "無限生成",
"Cancel generate forever": "停止無限生成",
......@@ -581,10 +594,9 @@
"Start drawing": "開始繪製",
"Description": "描述",
"Action": "行動",
"Aesthetic Gradients": "美術風格",
"aesthetic-gradients": "美術風格",
"stable-diffusion-webui-wildcards": "萬用字元",
"Dynamic Prompts": "動態提示",
"Aesthetic Gradients": "美術風格梯度",
"aesthetic-gradients": "美術風格梯度",
"Dynamic Prompts": "動態提示詞",
"images-browser": "圖庫瀏覽器",
"Inspiration": "靈感",
"Deforum": "Deforum",
......
......@@ -10,6 +10,7 @@ from modules.api.models import *
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
from modules.sd_samplers import all_samplers
from modules.extras import run_extras, run_pnginfo
from PIL import PngImagePlugin
from modules.sd_models import checkpoints_list
from modules.realesrgan_model import get_realesrgan_models
from typing import List
......@@ -34,9 +35,21 @@ def setUpscalers(req: dict):
def encode_pil_to_base64(image):
buffer = io.BytesIO()
image.save(buffer, format="png")
return base64.b64encode(buffer.getvalue())
with io.BytesIO() as output_bytes:
# Copy any text-only metadata
use_metadata = False
metadata = PngImagePlugin.PngInfo()
for key, value in image.info.items():
if isinstance(key, str) and isinstance(value, str):
metadata.add_text(key, value)
use_metadata = True
image.save(
output_bytes, "PNG", pnginfo=(metadata if use_metadata else None)
)
bytes_data = output_bytes.getvalue()
return base64.b64encode(bytes_data)
class Api:
......@@ -50,6 +63,7 @@ class Api:
self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse)
self.app.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=PNGInfoResponse)
self.app.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=ProgressResponse)
self.app.add_api_route("/sdapi/v1/interrogate", self.interrogateapi, methods=["POST"])
self.app.add_api_route("/sdapi/v1/interrupt", self.interruptapi, methods=["POST"])
self.app.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=OptionsModel)
self.app.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
......@@ -201,11 +215,24 @@ class Api:
return ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image)
def interrogateapi(self, interrogatereq: InterrogateRequest):
image_b64 = interrogatereq.image
if image_b64 is None:
raise HTTPException(status_code=404, detail="Image not found")
img = self.__base64_to_image(image_b64)
# Override object param
with self.queue_lock:
processed = shared.interrogator.interrogate(img)
return InterrogateResponse(caption=processed)
def interruptapi(self):
shared.state.interrupt()
return {}
def get_config(self):
options = {}
for key in shared.opts.data.keys():
......@@ -214,10 +241,14 @@ class Api:
options.update({key: shared.opts.data.get(key, shared.opts.data_labels.get(key).default)})
else:
options.update({key: shared.opts.data.get(key, None)})
return options
def set_config(self, req: OptionsModel):
# currently req has all options fields even if you send a dict like { "send_seed": false }, which means it will
# overwrite all options with default values.
raise RuntimeError('Setting options via API is not supported')
reqDict = vars(req)
for o in reqDict:
setattr(shared.opts, o, reqDict[o])
......@@ -233,13 +264,13 @@ class Api:
def get_upscalers(self):
upscalers = []
for upscaler in shared.sd_upscalers:
u = upscaler.scaler
upscalers.append({"name":u.name, "model_name":u.model_name, "model_path":u.model_path, "model_url":u.model_url})
return upscalers
def get_sd_models(self):
return [{"title":x.title, "model_name":x.model_name, "hash":x.hash, "filename": x.filename, "config": x.config} for x in checkpoints_list.values()]
......@@ -251,11 +282,11 @@ class Api:
def get_realesrgan_models(self):
return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)]
def get_promp_styles(self):
styleList = []
for k in shared.prompt_styles.styles:
style = shared.prompt_styles.styles[k]
style = shared.prompt_styles.styles[k]
styleList.append({"name":style[0], "prompt": style[1], "negative_prompr": style[2]})
return styleList
......
import inspect
from pydantic import BaseModel, Field, create_model
from typing import Any, Optional, Union
from typing import Any, Optional
from typing_extensions import Literal
from inflection import underscore
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
from modules.shared import sd_upscalers, opts, parser
from typing import List
from typing import Dict, List
API_NOT_ALLOWED = [
"self",
......@@ -65,6 +65,7 @@ class PydanticModelGenerator:
self._model_name = model_name
self._class_data = merge_class_params(class_instance)
self._model_def = [
ModelDef(
field=underscore(k),
......@@ -167,6 +168,12 @@ class ProgressResponse(BaseModel):
state: dict = Field(title="State", description="The current state snapshot")
current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.")
class InterrogateRequest(BaseModel):
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
class InterrogateResponse(BaseModel):
caption: str = Field(default=None, title="Caption", description="The generated caption for the image.")
fields = {}
for key, value in opts.data.items():
metadata = opts.data_labels.get(key)
......@@ -185,22 +192,22 @@ _options = vars(parser)['_option_string_actions']
for key in _options:
if(_options[key].dest != 'help'):
flag = _options[key]
_type = str
if(_options[key].default != None): _type = type(_options[key].default)
_type = str
if _options[key].default is not None: _type = type(_options[key].default)
flags.update({flag.dest: (_type,Field(default=flag.default, description=flag.help))})
FlagsModel = create_model("Flags", **flags)
class SamplerItem(BaseModel):
name: str = Field(title="Name")
aliases: list[str] = Field(title="Aliases")
options: dict[str, str] = Field(title="Options")
aliases: List[str] = Field(title="Aliases")
options: Dict[str, str] = Field(title="Options")
class UpscalerItem(BaseModel):
name: str = Field(title="Name")
model_name: str | None = Field(title="Model Name")
model_path: str | None = Field(title="Path")
model_url: str | None = Field(title="URL")
model_name: Optional[str] = Field(title="Model Name")
model_path: Optional[str] = Field(title="Path")
model_url: Optional[str] = Field(title="URL")
class SDModelItem(BaseModel):
title: str = Field(title="Title")
......@@ -211,23 +218,24 @@ class SDModelItem(BaseModel):
class HypernetworkItem(BaseModel):
name: str = Field(title="Name")
path: str | None = Field(title="Path")
path: Optional[str] = Field(title="Path")
class FaceRestorerItem(BaseModel):
name: str = Field(title="Name")
cmd_dir: str | None = Field(title="Path")
cmd_dir: Optional[str] = Field(title="Path")
class RealesrganItem(BaseModel):
name: str = Field(title="Name")
path: str | None = Field(title="Path")
scale: int | None = Field(title="Scale")
path: Optional[str] = Field(title="Path")
scale: Optional[int] = Field(title="Scale")
class PromptStyleItem(BaseModel):
name: str = Field(title="Name")
prompt: str | None = Field(title="Prompt")
negative_prompt: str | None = Field(title="Negative Prompt")
prompt: Optional[str] = Field(title="Prompt")
negative_prompt: Optional[str] = Field(title="Negative Prompt")
class ArtistItem(BaseModel):
name: str = Field(title="Name")
score: float = Field(title="Score")
category: str = Field(title="Category")
\ No newline at end of file
category: str = Field(title="Category")
......@@ -34,8 +34,11 @@ class Extension:
if repo is None or repo.bare:
self.remote = None
else:
self.remote = next(repo.remote().urls, None)
self.status = 'unknown'
try:
self.remote = next(repo.remote().urls, None)
self.status = 'unknown'
except Exception:
self.remote = None
def list_files(self, subdir, extension):
from modules import scripts
......
......@@ -22,6 +22,8 @@ from collections import defaultdict, deque
from statistics import stdev, mean
optimizer_dict = {optim_name : cls_obj for optim_name, cls_obj in inspect.getmembers(torch.optim, inspect.isclass) if optim_name != "Optimizer"}
class HypernetworkModule(torch.nn.Module):
multiplier = 1.0
activation_dict = {
......@@ -142,6 +144,8 @@ class Hypernetwork:
self.use_dropout = use_dropout
self.activate_output = activate_output
self.last_layer_dropout = kwargs['last_layer_dropout'] if 'last_layer_dropout' in kwargs else True
self.optimizer_name = None
self.optimizer_state_dict = None
for size in enable_sizes or []:
self.layers[size] = (
......@@ -163,6 +167,7 @@ class Hypernetwork:
def save(self, filename):
state_dict = {}
optimizer_saved_dict = {}
for k, v in self.layers.items():
state_dict[k] = (v[0].state_dict(), v[1].state_dict())
......@@ -178,8 +183,15 @@ class Hypernetwork:
state_dict['sd_checkpoint_name'] = self.sd_checkpoint_name
state_dict['activate_output'] = self.activate_output
state_dict['last_layer_dropout'] = self.last_layer_dropout
if self.optimizer_name is not None:
optimizer_saved_dict['optimizer_name'] = self.optimizer_name
torch.save(state_dict, filename)
if shared.opts.save_optimizer_state and self.optimizer_state_dict:
optimizer_saved_dict['hash'] = sd_models.model_hash(filename)
optimizer_saved_dict['optimizer_state_dict'] = self.optimizer_state_dict
torch.save(optimizer_saved_dict, filename + '.optim')
def load(self, filename):
self.filename = filename
......@@ -202,6 +214,18 @@ class Hypernetwork:
print(f"Activate last layer is set to {self.activate_output}")
self.last_layer_dropout = state_dict.get('last_layer_dropout', False)
optimizer_saved_dict = torch.load(self.filename + '.optim', map_location = 'cpu') if os.path.exists(self.filename + '.optim') else {}
self.optimizer_name = optimizer_saved_dict.get('optimizer_name', 'AdamW')
print(f"Optimizer name is {self.optimizer_name}")
if sd_models.model_hash(filename) == optimizer_saved_dict.get('hash', None):
self.optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None)
else:
self.optimizer_state_dict = None
if self.optimizer_state_dict:
print("Loaded existing optimizer from checkpoint")
else:
print("No saved optimizer exists in checkpoint")
for size, sd in state_dict.items():
if type(size) == int:
self.layers[size] = (
......@@ -219,11 +243,11 @@ class Hypernetwork:
def list_hypernetworks(path):
res = {}
for filename in glob.iglob(os.path.join(path, '**/*.pt'), recursive=True):
for filename in sorted(glob.iglob(os.path.join(path, '**/*.pt'), recursive=True)):
name = os.path.splitext(os.path.basename(filename))[0]
# Prevent a hypothetical "None.pt" from being listed.
if name != "None":
res[name] = filename
res[name + f"({sd_models.model_hash(filename)})"] = filename
return res
......@@ -358,6 +382,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
shared.state.textinfo = "Initializing hypernetwork training..."
shared.state.job_count = steps
hypernetwork_name = hypernetwork_name.rsplit('(', 1)[0]
filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt')
log_directory = os.path.join(log_directory, datetime.datetime.now().strftime("%Y-%m-%d"), hypernetwork_name)
......@@ -410,8 +435,22 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
weights = hypernetwork.weights()
for weight in weights:
weight.requires_grad = True
# if optimizer == "AdamW": or else Adam / AdamW / SGD, etc...
optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate)
# Here we use optimizer from saved HN, or we can specify as UI option.
if hypernetwork.optimizer_name in optimizer_dict:
optimizer = optimizer_dict[hypernetwork.optimizer_name](params=weights, lr=scheduler.learn_rate)
optimizer_name = hypernetwork.optimizer_name
else:
print(f"Optimizer type {hypernetwork.optimizer_name} is not defined!")
optimizer = torch.optim.AdamW(params=weights, lr=scheduler.learn_rate)
optimizer_name = 'AdamW'
if hypernetwork.optimizer_state_dict: # This line must be changed if Optimizer type can be different from saved optimizer.
try:
optimizer.load_state_dict(hypernetwork.optimizer_state_dict)
except RuntimeError as e:
print("Cannot resume from saved optimizer!")
print(e)
steps_without_grad = 0
......@@ -479,7 +518,11 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
# Before saving, change name to match current checkpoint.
hypernetwork_name_every = f'{hypernetwork_name}-{steps_done}'
last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork_name_every}.pt')
hypernetwork.optimizer_name = optimizer_name
if shared.opts.save_optimizer_state:
hypernetwork.optimizer_state_dict = optimizer.state_dict()
save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, last_saved_file)
hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory.
textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, len(ds), {
"loss": f"{previous_mean_loss:.7f}",
......@@ -542,8 +585,12 @@ Last saved image: {html.escape(last_saved_image)}<br/>
report_statistics(loss_dict)
filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt')
hypernetwork.optimizer_name = optimizer_name
if shared.opts.save_optimizer_state:
hypernetwork.optimizer_state_dict = optimizer.state_dict()
save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename)
del optimizer
hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory.
return hypernetwork, filename
def save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename):
......
......@@ -9,7 +9,7 @@ from modules import devices, sd_hijack, shared
from modules.hypernetworks import hypernetwork
not_available = ["hardswish", "multiheadattention"]
keys = ["linear"] + list(x for x in hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available)
keys = list(x for x in hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available)
def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False):
# Remove illegal characters from name.
......
......@@ -101,8 +101,8 @@ class LDSR:
down_sample_rate = target_scale / 4
wd = width_og * down_sample_rate
hd = height_og * down_sample_rate
width_downsampled_pre = int(wd)
height_downsampled_pre = int(hd)
width_downsampled_pre = int(np.ceil(wd))
height_downsampled_pre = int(np.ceil(hd))
if down_sample_rate != 1:
print(
......@@ -110,7 +110,12 @@ class LDSR:
im_og = im_og.resize((width_downsampled_pre, height_downsampled_pre), Image.LANCZOS)
else:
print(f"Down sample rate is 1 from {target_scale} / 4 (Not downsampling)")
logs = self.run(model["model"], im_og, diffusion_steps, eta)
# pad width and height to multiples of 64, pads with the edge values of image to avoid artifacts
pad_w, pad_h = np.max(((2, 2), np.ceil(np.array(im_og.size) / 64).astype(int)), axis=0) * 64 - im_og.size
im_padded = Image.fromarray(np.pad(np.array(im_og), ((0, pad_h), (0, pad_w), (0, 0)), mode='edge'))
logs = self.run(model["model"], im_padded, diffusion_steps, eta)
sample = logs["sample"]
sample = sample.detach().cpu()
......@@ -120,6 +125,9 @@ class LDSR:
sample = np.transpose(sample, (0, 2, 3, 1))
a = Image.fromarray(sample[0])
# remove padding
a = a.crop((0, 0) + tuple(np.array(im_og.size) * 4))
del model
gc.collect()
torch.cuda.empty_cache()
......
......@@ -3,6 +3,7 @@ import os
import sys
import traceback
localizations = {}
......@@ -16,6 +17,11 @@ def list_localizations(dirname):
localizations[fn] = os.path.join(dirname, file)
from modules import scripts
for file in scripts.list_scripts("localizations", ".json"):
fn, ext = os.path.splitext(file.filename)
localizations[fn] = file.path
def localization_js(current_localization_name):
fn = localizations.get(current_localization_name, None)
......
......@@ -23,11 +23,18 @@ def encode(*args):
class RestrictedUnpickler(pickle.Unpickler):
extra_handler = None
def persistent_load(self, saved_id):
assert saved_id[0] == 'storage'
return TypedStorage()
def find_class(self, module, name):
if self.extra_handler is not None:
res = self.extra_handler(module, name)
if res is not None:
return res
if module == 'collections' and name == 'OrderedDict':
return getattr(collections, name)
if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter']:
......@@ -52,7 +59,7 @@ class RestrictedUnpickler(pickle.Unpickler):
return set
# Forbid everything else.
raise pickle.UnpicklingError(f"global '{module}/{name}' is forbidden")
raise Exception(f"global '{module}/{name}' is forbidden")
allowed_zip_names = ["archive/data.pkl", "archive/version"]
......@@ -69,7 +76,7 @@ def check_zip_filenames(filename, names):
raise Exception(f"bad file inside {filename}: {name}")
def check_pt(filename):
def check_pt(filename, extra_handler):
try:
# new pytorch format is a zip file
......@@ -78,6 +85,7 @@ def check_pt(filename):
with z.open('archive/data.pkl') as file:
unpickler = RestrictedUnpickler(file)
unpickler.extra_handler = extra_handler
unpickler.load()
except zipfile.BadZipfile:
......@@ -85,16 +93,42 @@ def check_pt(filename):
# if it's not a zip file, it's an olf pytorch format, with five objects written to pickle
with open(filename, "rb") as file:
unpickler = RestrictedUnpickler(file)
unpickler.extra_handler = extra_handler
for i in range(5):
unpickler.load()
def load(filename, *args, **kwargs):
return load_with_extra(filename, *args, **kwargs)
def load_with_extra(filename, extra_handler=None, *args, **kwargs):
"""
this functon is intended to be used by extensions that want to load models with
some extra classes in them that the usual unpickler would find suspicious.
Use the extra_handler argument to specify a function that takes module and field name as text,
and returns that field's value:
```python
def extra(module, name):
if module == 'collections' and name == 'OrderedDict':
return collections.OrderedDict
return None
safe.load_with_extra('model.pt', extra_handler=extra)
```
The alternative to this is just to use safe.unsafe_torch_load('model.pt'), which as the name implies is
definitely unsafe.
"""
from modules import shared
try:
if not shared.cmd_opts.disable_safe_unpickle:
check_pt(filename)
check_pt(filename, extra_handler)
except pickle.UnpicklingError:
print(f"Error verifying pickled file from {filename}:", file=sys.stderr)
......
......@@ -46,25 +46,23 @@ class CFGDenoiserParams:
ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"])
callbacks_app_started = []
callbacks_model_loaded = []
callbacks_ui_tabs = []
callbacks_ui_settings = []
callbacks_before_image_saved = []
callbacks_image_saved = []
callbacks_cfg_denoiser = []
callback_map = dict(
callbacks_app_started=[],
callbacks_model_loaded=[],
callbacks_ui_tabs=[],
callbacks_ui_settings=[],
callbacks_before_image_saved=[],
callbacks_image_saved=[],
callbacks_cfg_denoiser=[]
)
def clear_callbacks():
callbacks_model_loaded.clear()
callbacks_ui_tabs.clear()
callbacks_ui_settings.clear()
callbacks_before_image_saved.clear()
callbacks_image_saved.clear()
callbacks_cfg_denoiser.clear()
for callback_list in callback_map.values():
callback_list.clear()
def app_started_callback(demo: Optional[Blocks], app: FastAPI):
for c in callbacks_app_started:
for c in callback_map['callbacks_app_started']:
try:
c.callback(demo, app)
except Exception:
......@@ -72,7 +70,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI):
def model_loaded_callback(sd_model):
for c in callbacks_model_loaded:
for c in callback_map['callbacks_model_loaded']:
try:
c.callback(sd_model)
except Exception:
......@@ -82,7 +80,7 @@ def model_loaded_callback(sd_model):
def ui_tabs_callback():
res = []
for c in callbacks_ui_tabs:
for c in callback_map['callbacks_ui_tabs']:
try:
res += c.callback() or []
except Exception:
......@@ -92,7 +90,7 @@ def ui_tabs_callback():
def ui_settings_callback():
for c in callbacks_ui_settings:
for c in callback_map['callbacks_ui_settings']:
try:
c.callback()
except Exception:
......@@ -100,7 +98,7 @@ def ui_settings_callback():
def before_image_saved_callback(params: ImageSaveParams):
for c in callbacks_before_image_saved:
for c in callback_map['callbacks_before_image_saved']:
try:
c.callback(params)
except Exception:
......@@ -108,7 +106,7 @@ def before_image_saved_callback(params: ImageSaveParams):
def image_saved_callback(params: ImageSaveParams):
for c in callbacks_image_saved:
for c in callback_map['callbacks_image_saved']:
try:
c.callback(params)
except Exception:
......@@ -116,7 +114,7 @@ def image_saved_callback(params: ImageSaveParams):
def cfg_denoiser_callback(params: CFGDenoiserParams):
for c in callbacks_cfg_denoiser:
for c in callback_map['callbacks_cfg_denoiser']:
try:
c.callback(params)
except Exception:
......@@ -129,17 +127,33 @@ def add_callback(callbacks, fun):
callbacks.append(ScriptCallback(filename, fun))
def remove_current_script_callbacks():
stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if len(stack) > 0 else 'unknown file'
if filename == 'unknown file':
return
for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.script == filename]:
callback_list.remove(callback_to_remove)
def remove_callbacks_for_function(callback_func):
for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]:
callback_list.remove(callback_to_remove)
def on_app_started(callback):
"""register a function to be called when the webui started, the gradio `Block` component and
fastapi `FastAPI` object are passed as the arguments"""
add_callback(callbacks_app_started, callback)
add_callback(callback_map['callbacks_app_started'], callback)
def on_model_loaded(callback):
"""register a function to be called when the stable diffusion model is created; the model is
passed as an argument"""
add_callback(callbacks_model_loaded, callback)
add_callback(callback_map['callbacks_model_loaded'], callback)
def on_ui_tabs(callback):
......@@ -152,13 +166,13 @@ def on_ui_tabs(callback):
title is tab text displayed to user in the UI
elem_id is HTML id for the tab
"""
add_callback(callbacks_ui_tabs, callback)
add_callback(callback_map['callbacks_ui_tabs'], callback)
def on_ui_settings(callback):
"""register a function to be called before UI settings are populated; add your settings
by using shared.opts.add_option(shared.OptionInfo(...)) """
add_callback(callbacks_ui_settings, callback)
add_callback(callback_map['callbacks_ui_settings'], callback)
def on_before_image_saved(callback):
......@@ -166,7 +180,7 @@ def on_before_image_saved(callback):
The callback is called with one argument:
- params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object.
"""
add_callback(callbacks_before_image_saved, callback)
add_callback(callback_map['callbacks_before_image_saved'], callback)
def on_image_saved(callback):
......@@ -174,7 +188,7 @@ def on_image_saved(callback):
The callback is called with one argument:
- params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing.
"""
add_callback(callbacks_image_saved, callback)
add_callback(callback_map['callbacks_image_saved'], callback)
def on_cfg_denoiser(callback):
......@@ -182,5 +196,4 @@ def on_cfg_denoiser(callback):
The callback is called with one argument:
- params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details.
"""
add_callback(callbacks_cfg_denoiser, callback)
add_callback(callback_map['callbacks_cfg_denoiser'], callback)
......@@ -3,7 +3,6 @@ import sys
import traceback
from collections import namedtuple
import modules.ui as ui
import gradio as gr
from modules.processing import StableDiffusionProcessing
......
......@@ -24,11 +24,15 @@ samplers_k_diffusion = [
('Heun', 'sample_heun', ['k_heun'], {}),
('DPM2', 'sample_dpm_2', ['k_dpm_2'], {}),
('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {}),
('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {}),
('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}),
('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {}),
('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {}),
('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}),
('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras'}),
('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras'}),
('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras'}),
('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}),
]
samplers_data_k_diffusion = [
......
......@@ -86,6 +86,10 @@ parser.add_argument("--nowebui", action='store_true', help="use api=True to laun
parser.add_argument("--ui-debug-mode", action='store_true', help="Don't load model to quickly launch UI")
parser.add_argument("--device-id", type=str, help="Select the default CUDA device to use (export CUDA_VISIBLE_DEVICES=0,1,etc might be needed before)", default=None)
parser.add_argument("--administrator", action='store_true', help="Administrator rights", default=False)
parser.add_argument("--cors-allow-origins", type=str, help="Allowed CORS origins", default=None)
parser.add_argument("--tls-keyfile", type=str, help="Partially enables TLS, requires --tls-certfile to fully function", default=None)
parser.add_argument("--tls-certfile", type=str, help="Partially enables TLS, requires --tls-keyfile to fully function", default=None)
parser.add_argument("--server-name", type=str, help="Sets hostname of server", default=None)
cmd_opts = parser.parse_args()
restricted_opts = {
......@@ -147,9 +151,9 @@ class State:
self.interrupted = True
def nextjob(self):
if opts.show_progress_every_n_steps == -1:
if opts.show_progress_every_n_steps == -1:
self.do_set_current_image()
self.job_no += 1
self.sampling_step = 0
self.current_image_sampling_step = 0
......@@ -198,7 +202,7 @@ class State:
return
if self.current_latent is None:
return
if opts.show_progress_grid:
self.current_image = sd_samplers.samples_to_image_grid(self.current_latent)
else:
......@@ -217,8 +221,6 @@ interrogator = modules.interrogate.InterrogateModels("interrogate")
face_restorers = []
localization.list_localizations(cmd_opts.localizations_dir)
def realesrgan_models_names():
import modules.realesrgan_model
......@@ -317,6 +319,7 @@ options_templates.update(options_section(('system', "System"), {
options_templates.update(options_section(('training', "Training"), {
"unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."),
"save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training can be resumed with HN itself and matching optim file."),
"dataset_filename_word_regex": OptionInfo("", "Filename word regex"),
"dataset_filename_join_string": OptionInfo(" ", "Filename join string"),
"training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}),
......@@ -406,7 +409,8 @@ class Options:
if key in self.data or key in self.data_labels:
assert not cmd_opts.freeze_settings, "changing settings is disabled"
comp_args = opts.data_labels[key].component_args
info = opts.data_labels.get(key, None)
comp_args = info.component_args if info else None
if isinstance(comp_args, dict) and comp_args.get('visible', True) is False:
raise RuntimeError(f"not possible to set {key} because it is restricted")
......
......@@ -174,9 +174,9 @@ def save_pil_to_file(pil_image, dir=None):
gr.processing_utils.save_pil_to_file = save_pil_to_file
def wrap_gradio_call(func, extra_outputs=None):
def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
def f(*args, extra_outputs_array=extra_outputs, **kwargs):
run_memmon = opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled
run_memmon = opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats
if run_memmon:
shared.mem_mon.monitor()
t = time.perf_counter()
......@@ -203,11 +203,18 @@ def wrap_gradio_call(func, extra_outputs=None):
res = extra_outputs_array + [f"<div class='error'>{plaintext_to_html(type(e).__name__+': '+str(e))}</div>"]
shared.state.skipped = False
shared.state.interrupted = False
shared.state.job_count = 0
if not add_stats:
return tuple(res)
elapsed = time.perf_counter() - t
elapsed_m = int(elapsed // 60)
elapsed_s = elapsed % 60
elapsed_text = f"{elapsed_s:.2f}s"
if (elapsed_m > 0):
if elapsed_m > 0:
elapsed_text = f"{elapsed_m}m "+elapsed_text
if run_memmon:
......@@ -225,10 +232,6 @@ def wrap_gradio_call(func, extra_outputs=None):
# last item is always HTML
res[-1] += f"<div class='performance'><p class='time'>Time taken: <wbr>{elapsed_text}</p>{vram_html}</div>"
shared.state.skipped = False
shared.state.interrupted = False
shared.state.job_count = 0
return tuple(res)
return f
......@@ -1138,7 +1141,7 @@ def create_ui(wrap_gradio_gpu_call):
outputs=[html, generation_info, html2],
)
with gr.Blocks() as modelmerger_interface:
with gr.Blocks(analytics_enabled=False) as modelmerger_interface:
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
gr.HTML(value="<p>A merger of the two checkpoints will be generated in your <b>checkpoint</b> directory.</p>")
......@@ -1158,7 +1161,7 @@ def create_ui(wrap_gradio_gpu_call):
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings()
with gr.Blocks() as train_interface:
with gr.Blocks(analytics_enabled=False) as train_interface:
with gr.Row().style(equal_height=False):
gr.HTML(value="<p style='margin-bottom: 0.7em'>See <b><a href=\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Textual-Inversion\">wiki</a></b> for detailed explanation.</p>")
......@@ -1423,15 +1426,14 @@ def create_ui(wrap_gradio_gpu_call):
if info.refresh is not None:
if is_quicksettings:
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {}))
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
else:
with gr.Row(variant="compact"):
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {}))
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
else:
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {}))
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
return res
......@@ -1442,7 +1444,7 @@ def create_ui(wrap_gradio_gpu_call):
opts.reorder()
def run_settings(*args):
changed = 0
changed = []
for key, value, comp in zip(opts.data_labels.keys(), args, components):
assert comp == dummy_component or opts.same_type(value, opts.data_labels[key].default), f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}"
......@@ -1452,18 +1454,20 @@ def create_ui(wrap_gradio_gpu_call):
continue
oldval = opts.data.get(key, None)
setattr(opts, key, value)
try:
setattr(opts, key, value)
except RuntimeError:
continue
if oldval != value:
if opts.data_labels[key].onchange is not None:
opts.data_labels[key].onchange()
changed += 1
opts.save(shared.config_filename)
return opts.dumpjson(), f'{changed} settings changed.'
changed.append(key)
try:
opts.save(shared.config_filename)
except RuntimeError:
return opts.dumpjson(), f'{len(changed)} settings changed without save: {", ".join(changed)}.'
return opts.dumpjson(), f'{len(changed)} settings changed: {", ".join(changed)}.'
def run_settings_single(value, key):
if not opts.same_type(value, opts.data_labels[key].default):
......@@ -1567,11 +1571,10 @@ def create_ui(wrap_gradio_gpu_call):
shared.state.need_restart = True
restart_gradio.click(
fn=request_restart,
_js='restart_reload',
inputs=[],
outputs=[],
_js='restart_reload'
)
if column is not None:
......@@ -1641,6 +1644,17 @@ def create_ui(wrap_gradio_gpu_call):
outputs=[component, text_settings],
)
component_keys = [k for k in opts.data_labels.keys() if k in component_dict]
def get_settings_values():
return [getattr(opts, key) for key in component_keys]
demo.load(
fn=get_settings_values,
inputs=[],
outputs=[component_dict[k] for k in component_keys],
)
def modelmerger(*args):
try:
results = modules.extras.run_modelmerger(*args)
......
......@@ -140,13 +140,15 @@ def install_extension_from_url(dirname, url):
shutil.rmtree(tmpdir, True)
def install_extension_from_index(url):
def install_extension_from_index(url, hide_tags):
ext_table, message = install_extension_from_url(None, url)
return refresh_available_extensions_from_data(), ext_table, message
code, _ = refresh_available_extensions_from_data(hide_tags)
return code, ext_table, message
def refresh_available_extensions(url):
def refresh_available_extensions(url, hide_tags):
global available_extensions
import urllib.request
......@@ -155,13 +157,25 @@ def refresh_available_extensions(url):
available_extensions = json.loads(text)
return url, refresh_available_extensions_from_data(), ''
code, tags = refresh_available_extensions_from_data(hide_tags)
return url, code, gr.CheckboxGroup.update(choices=tags), ''
def refresh_available_extensions_for_tags(hide_tags):
code, _ = refresh_available_extensions_from_data(hide_tags)
return code, ''
def refresh_available_extensions_from_data():
def refresh_available_extensions_from_data(hide_tags):
extlist = available_extensions["extensions"]
installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions}
tags = available_extensions.get("tags", {})
tags_to_hide = set(hide_tags)
hidden = 0
code = f"""<!-- {time.time()} -->
<table id="available_extensions">
<thead>
......@@ -178,17 +192,24 @@ def refresh_available_extensions_from_data():
name = ext.get("name", "noname")
url = ext.get("url", None)
description = ext.get("description", "")
extension_tags = ext.get("tags", [])
if url is None:
continue
if len([x for x in extension_tags if x in tags_to_hide]) > 0:
hidden += 1
continue
existing = installed_extension_urls.get(normalize_git_url(url), None)
install_code = f"""<input onclick="install_extension_from_index(this, '{html.escape(url)}')" type="button" value="{"Install" if not existing else "Installed"}" {"disabled=disabled" if existing else ""} class="gr-button gr-button-lg gr-button-secondary">"""
tags_text = ", ".join([f"<span class='extension-tag' title='{tags.get(x, '')}'>{x}</span>" for x in extension_tags])
code += f"""
<tr>
<td><a href="{html.escape(url)}">{html.escape(name)}</a></td>
<td><a href="{html.escape(url)}" target="_blank">{html.escape(name)}</a><br />{tags_text}</td>
<td>{html.escape(description)}</td>
<td>{install_code}</td>
</tr>
......@@ -199,7 +220,10 @@ def refresh_available_extensions_from_data():
</table>
"""
return code
if hidden > 0:
code += f"<p>Extension hidden: {hidden}</p>"
return code, list(tags)
def create_ui():
......@@ -238,21 +262,30 @@ def create_ui():
extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
with gr.Row():
hide_tags = gr.CheckboxGroup(value=["ads", "localization"], label="Hide extensions with tags", choices=["script", "ads", "localization"])
install_result = gr.HTML()
available_extensions_table = gr.HTML()
refresh_available_extensions_button.click(
fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update()]),
inputs=[available_extensions_index],
outputs=[available_extensions_index, available_extensions_table, install_result],
fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]),
inputs=[available_extensions_index, hide_tags],
outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result],
)
install_extension_button.click(
fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]),
inputs=[extension_to_install],
inputs=[extension_to_install, hide_tags],
outputs=[available_extensions_table, extensions_table, install_result],
)
hide_tags.change(
fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]),
inputs=[hide_tags],
outputs=[available_extensions_table, install_result]
)
with gr.TabItem("Install from URL"):
install_url = gr.Text(label="URL for extension's git repository")
install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto")
......
......@@ -57,10 +57,18 @@ class Upscaler:
self.scale = scale
dest_w = img.width * scale
dest_h = img.height * scale
for i in range(3):
if img.width > dest_w and img.height > dest_h:
break
shape = (img.width, img.height)
img = self.do_upscale(img, selected_model)
if shape == (img.width, img.height):
break
if img.width >= dest_w and img.height >= dest_h:
break
if img.width != dest_w or img.height != dest_h:
img = img.resize((int(dest_w), int(dest_h)), resample=LANCZOS)
......
......@@ -2,7 +2,7 @@ transformers==4.19.2
diffusers==0.3.0
basicsr==1.4.2
gfpgan==1.3.8
gradio==3.8
gradio==3.9
numpy==1.23.3
Pillow==9.2.0
realesrgan==0.3.0
......
......@@ -563,6 +563,11 @@ img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h
opacity: 0.5;
}
.extension-tag{
font-weight: bold;
font-size: 95%;
}
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
......
......@@ -5,11 +5,12 @@ import importlib
import signal
import threading
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from modules.paths import script_path
from modules import devices, sd_samplers, upscaler, extensions
from modules import devices, sd_samplers, upscaler, extensions, localization
import modules.codeformer_model as codeformer
import modules.extras
import modules.face_restoration
......@@ -27,14 +28,12 @@ import modules.txt2img
import modules.script_callbacks
import modules.ui
from modules import devices
from modules import modelloader
from modules.paths import script_path
from modules.shared import cmd_opts
import modules.hypernetworks.hypernetwork
queue_lock = threading.Lock()
server_name = "0.0.0.0" if cmd_opts.listen else cmd_opts.server_name
def wrap_queued_call(func):
def f(*args, **kwargs):
......@@ -58,11 +57,12 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
return res
return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs)
return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True)
def initialize():
extensions.list_extensions()
localization.list_localizations(cmd_opts.localizations_dir)
if cmd_opts.ui_debug_mode:
shared.sd_upscalers = upscaler.UpscalerLanczos().scalers
......@@ -85,6 +85,19 @@ def initialize():
shared.opts.onchange("sd_hypernetwork", wrap_queued_call(lambda: modules.hypernetworks.hypernetwork.load_hypernetwork(shared.opts.sd_hypernetwork)))
shared.opts.onchange("sd_hypernetwork_strength", modules.hypernetworks.hypernetwork.apply_strength)
if cmd_opts.tls_keyfile is not None and cmd_opts.tls_keyfile is not None:
try:
if not os.path.exists(cmd_opts.tls_keyfile):
print("Invalid path to TLS keyfile given")
if not os.path.exists(cmd_opts.tls_certfile):
print(f"Invalid path to TLS certfile: '{cmd_opts.tls_certfile}'")
except TypeError:
cmd_opts.tls_keyfile = cmd_opts.tls_certfile = None
print("TLS setup invalid, running webui without TLS")
else:
print("Running with TLS")
# make the program just exit at ctrl+c without waiting for anything
def sigint_handler(sig, frame):
print(f'Interrupted with signal {sig} in {frame}')
......@@ -93,6 +106,11 @@ def initialize():
signal.signal(signal.SIGINT, sigint_handler)
def setup_cors(app):
if cmd_opts.cors_allow_origins:
app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_methods=['*'])
def create_api(app):
from modules.api.api import Api
api = Api(app, queue_lock)
......@@ -114,6 +132,7 @@ def api_only():
initialize()
app = FastAPI()
setup_cors(app)
app.add_middleware(GZipMiddleware, minimum_size=1000)
api = create_api(app)
......@@ -131,8 +150,10 @@ def webui():
app, local_url, share_url = demo.launch(
share=cmd_opts.share,
server_name="0.0.0.0" if cmd_opts.listen else None,
server_name=server_name,
server_port=cmd_opts.port,
ssl_keyfile=cmd_opts.tls_keyfile,
ssl_certfile=cmd_opts.tls_certfile,
debug=cmd_opts.gradio_debug,
auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None,
inbrowser=cmd_opts.autolaunch,
......@@ -147,6 +168,8 @@ def webui():
# runnnig its code. We disable this here. Suggested by RyotaK.
app.user_middleware = [x for x in app.user_middleware if x.cls.__name__ != 'CORSMiddleware']
setup_cors(app)
app.add_middleware(GZipMiddleware, minimum_size=1000)
if launch_api:
......@@ -160,6 +183,9 @@ def webui():
print('Reloading extensions')
extensions.list_extensions()
localization.list_localizations(cmd_opts.localizations_dir)
print('Reloading custom scripts')
modules.scripts.reload_scripts()
print('Reloading modules: modules.ui')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment