Merge pull request #3755 from vladmandic/dev

merge dev
This commit is contained in:
Vladimir Mandic
2025-02-20 10:35:17 -05:00
committed by GitHub
152 changed files with 82548 additions and 1588 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ build/
dist/
# dynamically generated
/repositories/ip-instruct/
/repositories/deepseek-vl2/
# all dynamic stuff
/extensions/**/*
+2 -2
View File
@@ -23,5 +23,5 @@
url = https://github.com/ArtVentureX/sd-webui-agent-scheduler
ignore = dirty
[submodule "extensions-builtin/sdnext-modernui"]
path = extensions-builtin/sdnext-modernui
url = https://github.com/BinaryQuantumSoul/sdnext-modernui
path = extensions-builtin/sdnext-modernui
url = https://github.com/BinaryQuantumSoul/sdnext-modernui
+2
View File
@@ -23,12 +23,14 @@ ignore-paths=/usr/lib/.*$,
modules/k-diffusion,
modules/ldsr,
modules/meissonic,
modules/mod,
modules/omnigen,
modules/onnx_impl,
modules/pag,
modules/pixelsmith,
modules/prompt_parser_xhinker.py,
modules/pulid/eva_clip,
modules/ras,
modules/rife,
modules/schedulers,
modules/taesd,
+2
View File
@@ -17,12 +17,14 @@ exclude = [
"modules/k-diffusion",
"modules/ldsr",
"modules/meissonic",
"modules/mod",
"modules/omnigen",
"modules/pag",
"modules/pixelsmith",
"modules/postprocess/aurasr_arch.py",
"modules/prompt_parser_xhinker.py",
"modules/pulid/eva_clip",
"modules/ras",
"modules/rife",
"modules/schedulers",
"modules/segmoe",
+141
View File
@@ -1,5 +1,146 @@
# Change Log for SD.Next
## Update for 2025-02-20
### Highlight for 2025-02-20
We're back with another update with nearly 100 commits!
- Starting with massive UI update with full [localization](https://vladmandic.github.io/sdnext-docs/Locale/) for 8 languages
and 100+ new [hints](https://vladmandic.github.io/sdnext-docs/Hints/)
- Big update to [Docker](https://vladmandic.github.io/sdnext-docs/Docker/) containers
with support for all major compute platforms
- A lot of [outpainting](https://vladmandic.github.io/sdnext-docs/Outpaint/) goodies
- Support for new models: [AlphaVLLM Lumina 2](https://github.com/Alpha-VLLM/Lumina-Image-2.0) and [Ostris Flex.1-Alpha](https://huggingface.co/ostris/Flex.1-alpha)
- And new **Mixture-of-Diffusers** regional prompting & tiling pipeline
- Follow-up to last weeks **interrogate/captioning** rewrite
now with redesigned captioning UI, batch support, and much more
plus **JoyTag**, **JoyCaption**, **PaliGemma**, **ToriiGate**, **Ovis2** added to list of supported models
- Some changes to **prompt parsing** to allow more control as well as
more flexibility when mouting SDNext server to custom URL
- Of course, cumulative fixes...
*...and more* - see [changelog](https://github.com/vladmandic/sdnext/blob/dev/CHANGELOG.md) for full details!
### Details for 2025-02-20
- **User Interface**
- **Hints**
- added/updated 100+ ui hints!
- [hints](https://vladmandic.github.io/sdnext-docs/Hints/) documentation and contribution guide
- **Localization**
- full ui localization!
*english, croatian, spanish, french, italian, portuguese, chinese, japanese, korean, russian*
- set in *settings -> user interface -> language*
- [localization](https://vladmandic.github.io/sdnext-docs/Locale/) documentation
- **UI**
- force browser cache-invalidate on page load
- configurable request timeout
- modernui improve gallery styling
- modernui improve networks styling
- modernui support variable card size
- **Docs**
- New [Outpaint](https://vladmandic.github.io/sdnext-docs/Outpaint/) step-by-step guide
- Updated [Docker](https://github.com/vladmandic/sdnext/wiki/Docker) guide
includes build and publish and both local and cloud examples
- **Models**
- [AlphaVLLM Lumina 2](https://github.com/Alpha-VLLM/Lumina-Image-2.0)
new foundation model for image generation based o Gemma-2-2B text encoder and a flow-based diffusion transformer
fully supports offloading and on-the-fly quantization
simply select from *networks -> models -> reference*
- [Ostris Flex.1-Alpha](https://huggingface.co/ostris/Flex.1-alpha)
originally based on *Flux.1-Schnell*, but retrained and with different architecture
result is model smaller than *Flux.1-Dev*, but with similar capabilities
fully supports offloading and on-the-fly quantization
simply select from *networks -> models -> reference*
- **Functions**
- [Mixture-of-Diffusers](https://huggingface.co/posts/elismasilva/251775641926329)
Regional tiling type of a solution for SDXL models
select from *scripts -> mixture of diffusers*
- [Automatic Color Inpaint]
Automatically creates mask based on selected color and triggers inpaint
simply select in *scripts -> automatic color inpaint* when in img2img mode
- [RAS: Region-Adaptive Sampling](https://github.com/microsoft/RAS) *experimental*
Speeds up SD3.5 models by sampling only regions of interest
Enable in *settings -> pipeline modifiers -> ras*
- **Interrogate/Captioning**
- Redesigned captioning UI
split from Process tab into separate tab
split `clip` vs `vlm` models processing
direct *send-to* buttons on all tabs: txt/img/ctrl->process/caption, process/caption->txt/img/ctrl
- Advanced params:
VLM: *max-tokens, num-beams, temperature, top-k, top-p, do-sample*
CLiP: *min-length, max-length, chunk-size, min-flavors, max-flavors, flavor-count, num-beams*
params are auto-saved in `config.json` and used when using quick interrogate
params that are set to 0 mean use model defaults
- Batch processing: VLM and CLiP
for example, can be used to caption your training dataset in one go
add option to append to captions file, can be used to run multiple captioning models in sequence
add option to run recursively on all subfolders
add progress bar
- Add additional VLM models:
[JoyTag](https://huggingface.co/fancyfeast/joytag)
[JoyCaption 2](https://huggingface.co/fancyfeast/llama-joycaption-alpha-two-hf-llava)
[Google PaliGemma 2](https://huggingface.co/google/paligemma2-3b-pt-224) 3B
[ToriiGate 0.4](https://huggingface.co/Minthy/ToriiGate-v0.4-7B) 7B
[AIDC Ovis2](https://huggingface.co/AIDC-AI/Ovis2-1B) 1B/2B/4B
- *Note* some models require `flash-attn` to be installed
due to binary/build dependencies, it should not be done automatically,
see [flash-attn](https://github.com/Dao-AILab/flash-attention) for installation instructions
- **Docker**
- updated **CUDA** receipe to `torch==2.6.0` with `cuda==12.6` and add prebuilt image
- added **ROCm** receipe and prebuilt image
- added **IPEX** receipe and add prebuilt image
- added **OpenVINO** receipe and prebuilt image
- **System**
- improve **python==3.12** compatibility
- **Torch**
- for **zluda** set default to `torch==2.6.0+cu118`
- for **openvino** set default to `torch==2.6.0+cpu`
- **OpenVINO**
- update to `openvino==2025.0.0`
- improve upscaler compatibility
- enable upscaler compile by default
- fix shape mismatch errors on too many resolution changes
- **ZLUDA**
- update to `zluda==3.8.8`
- **Other**
- **Asymmetric tiling**
allows for configurable image tiling for x/y axis separately
enable in *scripts -> asymmetric tiling*
*note*: traditional symmetric tiling is achieved by setting circular mode for both x and y
- **Styles**
ability to save and/or restore prompts before or after parsing of wildcards
set in *settings -> networks -> styles*
- **Access tokens**
persist *models -> hugginface -> token*
persist *models -> civitai -> token*
- global switch to lancosz method for all interal resize ops and bicubic for interpolation ops
- **Text encoder**
add advanced per-model options for text encoder
set in *settings -> text encoder -> Optional*
- **Subpath**
allow setting additional mount subpath over which server url will be accessible
set in *settings -> user interface*
- **Prompt parsing**
better handling of prompt parsing when using masking char `\`
- **Fixes**
- update torch nightly urls
- docs/wiki always use relative links
- ui use correct timezone for log display
- ui improve settings search behavior
- ui log scroll to bottom
- ui fix send to inpaint/sketch
- modernui add control init image toggle
- modernui fix sampler advanced options
- outpaint fixes
- validate output before hires/refine
- scheduler fix sigma index out of bounds
- force pydantic version reinstall/reload
- multi-unit when using controlnet-union
- pulid with hidiffusion
- api: stricter access control
- api: universal handle mount subpaths
## Update for 2025-02-05
- refresh dev/master branches
+5
View File
@@ -24,6 +24,8 @@
## SD.Next Features
All individual features are not listed here, instead check [ChangeLog](CHANGELOG.md) for full list of changes
- Fully localized:
**English | Chinese | Russian | Spanish | German | French | Italian | Portuguese | Japanese | Korean**
- Multiple UIs!
**Standard | Modern**
- Multiple [diffusion models](https://vladmandic.github.io/sdnext-docs/Model-Support/)!
@@ -34,6 +36,7 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG
- Optimized processing with latest `torch` developments with built-in support for model compile, quantize and compress
Compile backends: *Triton | StableFast | DeepCache | OneDiff | TeaCache | etc.*
Quantization and compression methods: *BitsAndBytes | TorchAO | Optimum-Quanto | NNCF*
- **Interrogate/Captioning** with 150+ **OpenCLiP** models and 20+ built-in **VLMs**
- Built-in queue management
- Built in installer with automatic updates and dependency management
- Mobile compatible
@@ -68,6 +71,8 @@ SD.Next supports broad range of models: [supported models](https://vladmandic.gi
- *ONNX/Olive*
- *AMD* GPUs on Windows using **ZLUDA** libraries
Plus Docker container receipes for: [CUDA, ROCm, Intel IPEX and OpenVINO](https://vladmandic.github.io/sdnext-docs/Docker/)
## Getting started
- Get started with **SD.Next** by following the [installation instructions](https://vladmandic.github.io/sdnext-docs/Installation/)
+18 -21
View File
@@ -2,29 +2,26 @@
Main ToDo list can be found at [GitHub projects](https://github.com/users/vladmandic/projects)
## Pending
- LoRA direct with caching
- Previewer issues
- Redesign postprocessing
## Future Candidates
- Flux NF4 loader: <https://github.com/huggingface/diffusers/issues/9996>
- IPAdapter negative: <https://github.com/huggingface/diffusers/discussions/7167>
- Control API enhance scripts compatibility
- PixelSmith: <https://github.com/Thanos-DB/Pixelsmith>
- Redesign postprocessing
- Flux NF4 loader: <https://github.com/huggingface/diffusers/issues/9996>
- IPAdapter negative: <https://github.com/huggingface/diffusers/discussions/7167>
- Control API enhance scripts compatibility
- CogView4
## Code TODO
- TODO install: enable ROCm for windows when available
- TODO resize image: enable full VAE mode for resize-latent
- TODO processing: remove duplicate mask params
- TODO flux: fix loader for civitai nf4 models
- TODO model loader: implement model in-memory caching
- TODO hypertile: vae breaks when using non-standard sizes
- TODO model load: force-reloading entire model as loading transformers only leads to massive memory usage
- TODO lora load: direct with bnb
- TODO lora make: support quantized flux
- TODO control: support scripts via api
- TODO modernui: monkey-patch for missing tabs.select event
- flux: loader for civitai nf4 models (fixme)
- hypertile: vae breaks when using non-standard sizes (fixme)
- install: enable ROCm for windows when available (fixme)
- lora make support quantized flux (fixme)
- lora: add other quantization types (fixme)
- model load: force-reloading entire model as loading transformers only leads to massive memory usage (fixme)
- model loader: implement model in-memory caching (fixme)
- modernui: monkey-patch for missing tabs.select event (fixme)
- processing: remove duplicate mask params (fixme)
- resize image: enable full VAE mode for resize-latent (fixme)
- sana: fails when quantized (fixme)
- support scripts via api (fixme)
- transformer from-single-file with quant (fixme)
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env python
import os
import sys
import logging
import torch
import diffusers
import safetensors
import safetensors.torch as sf
log = logging.getLogger("sd")
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s | %(message)s')
def convert(model_id, output_name):
if os.path.exists(output_name):
log.error(f'Output already exists: {output_name}')
return
pipe = diffusers.DiffusionPipeline.from_pretrained(model_id)
metadata = { 'model_id': model_id }
model = {}
model['state_dict'] = vars(pipe)['_internal_dict']
for k in model['state_dict'].keys():
# print(k, getattr(pipe, k))
model[k] = getattr(pipe, k)
sf.save_model(model, output_name, metadata=metadata)
# log.info(f'Saved model: {output_name}')
if __name__ == "__main__":
sys.argv.pop(0)
if len(sys.argv) < 2:
log.info('Usage: hf-convert.py <model_id> <output_name>')
sys.exit(1)
log.debug(f'Packages: torch={torch.__version__} diffusers={diffusers.__version__} safetensors={safetensors.__version__}')
convert(sys.argv[0], sys.argv[1])
-1
View File
@@ -29,4 +29,3 @@ if __name__ == "__main__":
print('=== BEGIN ===')
print(f'{b64}')
print('=== END ===')
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python
# Remove the entries that no longer exist in locale from override.
import sys
import json
from rich import print # pylint: disable=redefined-builtin
if __name__ == "__main__":
sys.argv.pop(0)
if len(sys.argv) == 0:
print('Invalid parameters.')
sys.exit(1)
filename = sys.argv[0]
labels = []
override = None
try:
with open('html/locale_en.json', 'r', encoding="utf-8") as f:
locale = json.load(f)
for v in locale.values():
for item in v:
labels.append(item['label'])
with open(filename, 'r', encoding="utf-8") as f:
override = json.load(f)
except Exception:
print('Invalid file format.')
sys.exit(1)
with open(filename, 'w', encoding="utf-8") as f:
json.dump([item for item in override if item['label'] in labels], f, ensure_ascii=False)
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env node
// script used to localize sdnext ui and hints to multiple languages using google gemini ai
const fs = require('fs');
const process = require('process');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const api_key = process.env.GOOGLE_AI_API_KEY;
const model = 'gemini-2.0-flash-exp';
const prompt = `
Translate attached JSON from English to {language} using following rules: fields id and label should be preserved from original, field localized should be a translated version of field label and field hint should be translated in-place.
Every JSON entry should have id, label, localized and hint fields. Output should be pure JSON without any additional text. To better match translation, context of the text is related to Stable Diffusion and topic of Generative AI.`;
const languages = {
hr: 'Croatian',
de: 'German',
es: 'Spanish',
fr: 'French',
it: 'Italian',
pt: 'Portuguese',
zh: 'Chinese',
ja: 'Japanese',
ko: 'Korean',
ru: 'Russian',
};
const chunkLines = 100;
async function localize() {
if (!api_key || api_key.length < 10) {
console.error('localize: set GOOGLE_AI_API_KEY env variable with your API key');
process.exit();
}
const genAI = new GoogleGenerativeAI(api_key);
const instance = genAI.getGenerativeModel({ model });
const raw = fs.readFileSync('html/locale_en.json');
const json = JSON.parse(raw);
for (const locale of Object.keys(languages)) {
const lang = languages[locale];
const target = prompt.replace('{language}', lang).trim();
const output = {};
const fn = `html/locale_${locale}.json`;
for (const section of Object.keys(json)) {
const data = json[section];
output[section] = [];
for (let i = 0; i < data.length; i += chunkLines) {
let markdown;
try {
const chunk = data.slice(i, i + chunkLines);
const result = await instance.generateContent([target, JSON.stringify(chunk)]);
markdown = result.response.text();
const text = markdown.replaceAll('```', '').replace(/^.*\n/, '');
const parsed = JSON.parse(text);
output[section].push(...parsed);
console.log(`localize: locale=${locale} lang=${lang} section=${section} chunk=${chunk.length} output=${output[section].length} fn=${fn}`);
} catch (err) {
console.error('localize:', err);
console.error('localize input:', { target, section, i });
console.error('localize output:', { markdown });
}
}
const txt = JSON.stringify(output, null, 2);
fs.writeFileSync(fn, txt);
}
}
}
localize();
+1 -1
View File
@@ -7,7 +7,7 @@ from rich import print # pylint: disable=redefined-builtin
if __name__ == "__main__":
sys.argv.pop(0)
fn = sys.argv[0] if len(sys.argv) > 0 else 'locale_en.json'
fn = sys.argv[0] if len(sys.argv) > 0 else 'html/locale_en.json'
if not os.path.isfile(fn):
print(f'File not found: {fn}')
sys.exit(1)
+3 -3
View File
@@ -2,7 +2,7 @@
# docs: <https://github.com/vladmandic/sdnext/wiki/Docker>
# base image
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
FROM pytorch/pytorch:2.6.0-cuda12.6-cudnn9-runtime
# metadata
LABEL org.opencontainers.image.vendor="SD.Next"
@@ -13,7 +13,7 @@ LABEL org.opencontainers.image.source="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.title="SD.Next"
LABEL org.opencontainers.image.description="SD.Next: Advanced Implementation of Stable Diffusion and other Diffusion-based generative image models"
LABEL org.opencontainers.image.base.name="https://hub.docker.com/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime"
LABEL org.opencontainers.image.base.name="https://hub.docker.com/pytorch/pytorch:2.6.0-cuda12.6-cudnn9-runtime"
LABEL org.opencontainers.image.version="latest"
# minimum install
@@ -29,8 +29,8 @@ WORKDIR /app
# stop pip and uv from caching
ENV PIP_NO_CACHE_DIR=true
ENV PIP_ROOT_USER_ACTION=ignore
ENV UV_NO_CACHE=true
ENV PIP_ROOT_USER_ACTION=ignore
# disable model hashing for faster startup
ENV SD_NOHASHING=true
# set data directories
+98
View File
@@ -0,0 +1,98 @@
# SD.Next IPEX Dockerfile
# docs: <https://github.com/vladmandic/sdnext/wiki/Docker>
# base image
FROM ubuntu:noble
# metadata
LABEL org.opencontainers.image.vendor="SD.Next"
LABEL org.opencontainers.image.authors="disty0"
LABEL org.opencontainers.image.url="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.documentation="https://github.com/vladmandic/sdnext/wiki/Docker"
LABEL org.opencontainers.image.source="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.title="SD.Next IPEX"
LABEL org.opencontainers.image.description="SD.Next: Advanced Implementation of Stable Diffusion and other Diffusion-based generative image models"
LABEL org.opencontainers.image.base.name="https://hub.docker.com/_/ubuntu:noble"
LABEL org.opencontainers.image.version="latest"
# essentials
RUN apt-get update && \
apt-get install -y --no-install-recommends --fix-missing \
software-properties-common \
build-essential \
ca-certificates \
wget \
gpg \
git
# intel compute runtime
RUN wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg
RUN echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu noble client" | tee /etc/apt/sources.list.d/intel-gpu-noble.list
RUN apt-get update
RUN apt-get install -y --no-install-recommends --fix-missing \
intel-opencl-icd \
libze-intel-gpu1 \
libze1
# required by pytorch / ipex
RUN apt-get install -y --no-install-recommends --fix-missing \
libgl1 \
libglib2.0-0 \
libgomp1
# python3.12
RUN apt-get install -y --no-install-recommends --fix-missing \
python3 \
python3-dev \
python3-venv \
python3-pip
# jemalloc is not required but it is highly recommended (also used with optional ipexrun)
RUN apt-get install -y --no-install-recommends --fix-missing libjemalloc-dev
ENV LD_PRELOAD=libjemalloc.so.2
# cleanup
RUN /usr/sbin/ldconfig
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# stop pip and uv from caching
ENV PIP_NO_CACHE_DIR=true
ENV UV_NO_CACHE=true
# set paths to use with sdnext
ENV SD_DOCKER=true
ENV SD_DATADIR="/mnt/data"
ENV SD_MODELSDIR="/mnt/models"
ENV venv_dir="/mnt/python/venv"
# paths used by sdnext can be a volume if necessary
#VOLUME [ "/app" ]
#VOLUME [ "/mnt/data" ]
#VOLUME [ "/mnt/models" ]
#VOLUME [ "/mnt/python" ]
#VOLUME [ "/root/.cache/huggingface" ]
# intel specific environment variables
#ENV IPEX_SDPA_SLICE_TRIGGER_RATE=1
#ENV IPEX_ATTENTION_SLICE_RATE=0.5
#ENV IPEX_FORCE_ATTENTION_SLICE=-1
#ENV IPEXRUN=False
# git clone and run sdnext
RUN echo '#!/bin/bash\ngit status || git clone https://github.com/vladmandic/sdnext.git .\n/app/webui.sh "$@"' | tee /bin/startup.sh
RUN chmod 755 /bin/startup.sh
# actually run sdnext
WORKDIR /app
ENTRYPOINT [ "startup.sh", "-f", "--use-ipex", "--uv", "--listen", "--debug", "--api-log", "--log", "sdnext.log" ]
# expose port
EXPOSE 7860
# healthcheck function
# HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 CMD curl --fail http://localhost:7860/sdapi/v1/status || exit 1
# stop signal
STOPSIGNAL SIGINT
+88
View File
@@ -0,0 +1,88 @@
# SD.Next OpenVINO Dockerfile
# docs: <https://github.com/vladmandic/sdnext/wiki/Docker>
# base image
FROM ubuntu:noble
# metadata
LABEL org.opencontainers.image.vendor="SD.Next"
LABEL org.opencontainers.image.authors="disty0"
LABEL org.opencontainers.image.url="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.documentation="https://github.com/vladmandic/sdnext/wiki/Docker"
LABEL org.opencontainers.image.source="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.title="SD.Next OpenVINO"
LABEL org.opencontainers.image.description="SD.Next: Advanced Implementation of Stable Diffusion and other Diffusion-based generative image models"
LABEL org.opencontainers.image.base.name="https://hub.docker.com/_/ubuntu:noble"
LABEL org.opencontainers.image.version="latest"
# essentials
RUN apt-get update && \
apt-get install -y --no-install-recommends --fix-missing \
software-properties-common \
build-essential \
ca-certificates \
wget \
gpg \
git
# intel compute runtime
RUN wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg
RUN echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu noble client" | tee /etc/apt/sources.list.d/intel-gpu-noble.list
RUN apt-get update
RUN apt-get install -y --no-install-recommends --fix-missing \
intel-opencl-icd \
libze-intel-gpu1 \
libze1
# required by pytorch / ipex
RUN apt-get install -y --no-install-recommends --fix-missing \
libgl1 \
libglib2.0-0 \
libgomp1
# python3.12
RUN apt-get install -y --no-install-recommends --fix-missing \
python3 \
python3-dev \
python3-venv \
python3-pip
# cleanup
RUN /usr/sbin/ldconfig
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# stop pip and uv from caching
ENV PIP_NO_CACHE_DIR=true
ENV UV_NO_CACHE=true
# set paths to use with sdnext
ENV SD_DOCKER=true
ENV SD_DATADIR="/mnt/data"
ENV SD_MODELSDIR="/mnt/models"
ENV venv_dir="/mnt/python/venv"
# paths used by sdnext can be a volume if necessary
#VOLUME [ "/app" ]
#VOLUME [ "/mnt/data" ]
#VOLUME [ "/mnt/models" ]
#VOLUME [ "/mnt/python" ]
#VOLUME [ "/root/.cache/huggingface" ]
# git clone and run sdnext
RUN echo '#!/bin/bash\ngit status || git clone https://github.com/vladmandic/sdnext.git .\n/app/webui.sh "$@"' | tee /bin/startup.sh
RUN chmod 755 /bin/startup.sh
# actually run sdnext
WORKDIR /app
ENTRYPOINT [ "startup.sh", "-f", "--use-openvino", "--uv", "--listen", "--debug", "--api-log", "--log", "sdnext.log" ]
# expose port
EXPOSE 7860
# healthcheck function
# HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 CMD curl --fail http://localhost:7860/sdapi/v1/status || exit 1
# stop signal
STOPSIGNAL SIGINT
+84
View File
@@ -0,0 +1,84 @@
# SD.Next ROCm Dockerfile
# docs: <https://github.com/vladmandic/sdnext/wiki/Docker>
# base image
# rocm runtime (3gb)
FROM rocm/dev-ubuntu-24.04:6.3.2
# rocm complete (32gb), required to build flash_atten
#FROM rocm/dev-ubuntu-24.04:6.3.2-complete
# metadata
LABEL org.opencontainers.image.vendor="SD.Next"
LABEL org.opencontainers.image.authors="disty0"
LABEL org.opencontainers.image.url="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.documentation="https://github.com/vladmandic/sdnext/wiki/Docker"
LABEL org.opencontainers.image.source="https://github.com/vladmandic/sdnext/"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.title="SD.Next ROCm"
LABEL org.opencontainers.image.description="SD.Next: Advanced Implementation of Stable Diffusion and other Diffusion-based generative image models"
LABEL org.opencontainers.image.base.name="https://hub.docker.com/r/rocm/dev-ubuntu-24.04:6.3.2"
LABEL org.opencontainers.image.version="latest"
# essentials
RUN apt-get update && \
apt-get install -y --no-install-recommends --fix-missing \
software-properties-common \
build-essential \
ca-certificates \
wget \
gpg \
git
# python3.12
RUN apt-get install -y --no-install-recommends --fix-missing \
python3 \
python3-dev \
python3-venv \
python3-pip
# jemalloc is not required but it is highly recommended
RUN apt-get install -y --no-install-recommends --fix-missing libjemalloc-dev
ENV LD_PRELOAD=libjemalloc.so.2
# cleanup
RUN /usr/sbin/ldconfig
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# stop pip and uv from caching
ENV PIP_NO_CACHE_DIR=true
ENV UV_NO_CACHE=true
# set paths to use with sdnext
ENV SD_DOCKER=true
ENV SD_DATADIR="/mnt/data"
ENV SD_MODELSDIR="/mnt/models"
ENV venv_dir="/mnt/python/venv"
# paths used by sdnext can be a volume if necessary
#VOLUME [ "/app" ]
#VOLUME [ "/mnt/data" ]
#VOLUME [ "/mnt/models" ]
#VOLUME [ "/mnt/python" ]
#VOLUME [ "/root/.cache/huggingface" ]
# override gpu architecture for unsupported gpus
#ENV HSA_OVERRIDE_GFX_VERSION=10.0.0
# git clone and run sdnext
RUN echo '#!/bin/bash\ngit status || git clone https://github.com/vladmandic/sdnext.git .\n/app/webui.sh "$@"' | tee /bin/startup.sh
RUN chmod 755 /bin/startup.sh
# actually run sdnext
WORKDIR /app
ENTRYPOINT [ "startup.sh", "-f", "--use-rocm", "--uv", "--listen", "--debug", "--api-log", "--log", "sdnext.log" ]
# expose port
EXPOSE 7860
# healthcheck function
# HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 CMD curl --fail http://localhost:7860/sdapi/v1/status || exit 1
# stop signal
STOPSIGNAL SIGINT
+7556
View File
File diff suppressed because it is too large Load Diff
+1134 -194
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556 -644
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+7556
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
[
]
+3
View File
@@ -0,0 +1,3 @@
[
{ "id": "", "label": "Reprocess", "localized": "Ponovi", "hint": "Ponovno obradite prethodne generacije koristeći različite parametre" }
]
File diff suppressed because one or more lines are too long
+14
View File
@@ -179,6 +179,13 @@
"skip": true,
"extras": "sampler: Default, cfg_scale: 3.5"
},
"Ostris Flex.1 Alpha": {
"path": "ostris/Flex.1-alpha",
"preview": "ostris--Flex.1-alpha.jpg",
"desc": "Flex.1 alpha is a pre-trained base 8 billion parameter rectified flow transformer capable of generating images from text descriptions. It has a similar architecture to FLUX.1-dev, but with fewer double transformer blocks (8 vs 19)",
"skip": true,
"extras": "sampler: Default, cfg_scale: 3.5"
},
"NVLabs Sana 1.6B 4k": {
"path": "Efficient-Large-Model/Sana_1600M_4Kpx_BF16_diffusers",
@@ -302,6 +309,13 @@
"skip": true,
"extras": "sampler: Default"
},
"AlphaVLLM Lumina 2": {
"path": "Alpha-VLLM/Lumina-Image-2.0",
"desc": "A Unified and Efficient Image Generative Model. Lumina-Image-2.0 is a 2 billion parameter flow-based diffusion transformer capable of generating images from text descriptions.",
"preview": "Alpha-VLLM--Lumina-Image-2.0.jpg",
"skip": true,
"extras": "sampler: Default"
},
"Kwai Kolors": {
"path": "Kwai-Kolors/Kolors-diffusers",
+48 -32
View File
@@ -4,6 +4,7 @@ import sys
import json
import time
import shutil
import locale
import logging
import platform
import subprocess
@@ -250,7 +251,7 @@ def package_spec(package):
# check if package is installed
@lru_cache()
def installed(package, friendly: str = None, reload = False, quiet = False):
def installed(package, friendly: str = None, reload = False, quiet = False): # pylint: disable=redefined-outer-name
t_start = time.time()
ok = True
try:
@@ -307,6 +308,16 @@ def uninstall(package, quiet = False):
return res
def run(cmd: str, arg: str):
result = subprocess.run(f'"{cmd}" {arg}', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
txt = result.stdout.decode(encoding="utf8", errors="ignore")
if len(result.stderr) > 0:
txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore")
txt = txt.strip()
debug(f'Exec {cmd}: {txt}')
return txt
@lru_cache()
def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True):
t_start = time.time()
@@ -343,15 +354,16 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True):
# install package using pip if not already installed
@lru_cache()
def install(package, friendly: str = None, ignore: bool = False, reinstall: bool = False, no_deps: bool = False, quiet: bool = False):
def install(package, friendly: str = None, ignore: bool = False, reinstall: bool = False, no_deps: bool = False, quiet: bool = False, force: bool = False):
t_start = time.time()
res = ''
if args.reinstall or args.upgrade:
global quick_allowed # pylint: disable=global-statement
quick_allowed = False
if args.reinstall or reinstall or not installed(package, friendly, quiet=quiet):
if (args.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)):
deps = '' if not no_deps else '--no-deps '
res = pip(f"install{' --upgrade' if not args.uv else ''} {deps}{package}", ignore=ignore, uv=package != "uv" and not package.startswith('git+'))
cmd = f"install{' --upgrade' if not args.uv else ''}{' --force' if force else ''} {deps}{package}"
res = pip(cmd, ignore=ignore, uv=package != "uv" and not package.startswith('git+'))
try:
importlib.reload(pkg_resources)
except Exception:
@@ -362,13 +374,12 @@ def install(package, friendly: str = None, ignore: bool = False, reinstall: bool
# execute git command
@lru_cache()
def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = False):
def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = False): # pylint: disable=unused-argument
t_start = time.time()
if args.skip_git:
return ''
if optional:
if 'google.colab' in sys.modules:
return ''
if 'google.colab' in sys.modules:
return ''
git_cmd = os.environ.get('GIT', "git")
if git_cmd != "git":
git_cmd = os.path.abspath(git_cmd)
@@ -481,6 +492,7 @@ def get_platform():
'system': platform.system(),
'release': release,
'python': platform.python_version(),
'locale': locale.getlocale(),
'docker': os.environ.get('SD_DOCKER', None) is not None,
# 'host': platform.node(),
# 'version': platform.version(),
@@ -520,7 +532,7 @@ def check_diffusers():
t_start = time.time()
if args.skip_all or args.skip_git:
return
sha = 'f63d32233f402bd603da8f3aa385aecb9c3d8809' # diffusers commit hash
sha = 'b75b204a584e29ebf4e80a61be11458e9ed56e3e' # diffusers commit hash
pkg = pkg_resources.working_set.by_key.get('diffusers', None)
minor = int(pkg.version.split('.')[1] if pkg is not None else 0)
cur = opts.get('diffusers_version', '') if minor > 0 else ''
@@ -553,16 +565,17 @@ def install_cuda():
log.info('CUDA: nVidia toolkit detected')
ts('cuda', t_start)
if args.use_nightly:
cmd = os.environ.get('TORCH_COMMAND', 'pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu128 --extra-index-url https://download.pytorch.org/whl/nightly/cu126')
cmd = os.environ.get('TORCH_COMMAND', 'pip install --upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu128 --extra-index-url https://download.pytorch.org/whl/nightly/cu126')
else:
cmd = os.environ.get('TORCH_COMMAND', 'torch==2.6.0+cu126 torchvision==0.21.0+cu126 --index-url https://download.pytorch.org/whl/cu126')
return cmd
def install_rocm_zluda():
torch_command = ''
t_start = time.time()
if args.skip_all or args.skip_requirements:
return None
return torch_command
from modules import rocm
if not rocm.is_installed:
log.warning('ROCm: could not find ROCm toolkit installed')
@@ -604,7 +617,6 @@ def install_rocm_zluda():
if device is not None:
msg += f', using agent {device.name}'
log.info(msg)
torch_command = ''
if sys.platform == "win32": # TODO install: enable ROCm for windows when available
check_python(supported_minors=[10, 11], reason='ZLUDA backend requires Python 3.10 or 3.11')
@@ -617,11 +629,11 @@ def install_rocm_zluda():
error = None
from modules import zluda_installer
zluda_installer.set_default_agent(device)
try:
if args.reinstall or zluda_installer.is_old_zluda():
if args.reinstall or zluda_installer.is_reinstall_needed():
zluda_installer.uninstall()
zluda_installer.install()
zluda_installer.set_default_agent(device)
except Exception as e:
error = e
log.warning(f'Failed to install ZLUDA: {e}')
@@ -633,7 +645,7 @@ def install_rocm_zluda():
zluda_installer.set_blaslt_enabled(device.blaslt_supported)
zluda_installer.make_copy()
zluda_installer.load()
torch_command = os.environ.get('TORCH_COMMAND', f'torch=={zluda_installer.get_default_torch_version(device)} torchvision --index-url https://download.pytorch.org/whl/cu118')
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.6.0 torchvision --index-url https://download.pytorch.org/whl/cu118')
log.info(f'Using ZLUDA in {zluda_installer.path}')
except Exception as e:
error = e
@@ -643,6 +655,10 @@ def install_rocm_zluda():
torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision')
else:
check_python(supported_minors=[9, 10, 11, 12], reason='ROCm backend requires a Python version between 3.9 and 3.12')
if os.environ.get("TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL", None) is None:
os.environ.setdefault('TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL', '1')
if args.use_nightly:
if rocm.version is None or float(rocm.version) >= 6.3: # assume the latest if version check fails
torch_command = os.environ.get('TORCH_COMMAND', '--pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm6.3')
@@ -667,13 +683,6 @@ def install_rocm_zluda():
# older rocm (5.7) uses torch 2.3 or older
torch_command = os.environ.get('TORCH_COMMAND', f'torch torchvision --index-url https://download.pytorch.org/whl/rocm{rocm.version}')
if installed("torch") and device is not None:
if 'Flash attention' in opts.get('sdp_options', ''):
if not installed('flash-attn'):
install(rocm.get_flash_attention_command(device), reinstall=True)
#elif not args.experimental:
# uninstall('flash-attn')
if device is not None and rocm.version != "6.2" and rocm.get_blaslt_enabled():
log.debug(f'ROCm hipBLASLt: arch={device.name} available={device.blaslt_supported}')
rocm.set_blaslt_enabled(device.blaslt_supported)
@@ -719,8 +728,6 @@ def install_ipex(torch_command):
else:
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.6.0+xpu torchvision==0.21.0+xpu --index-url https://download.pytorch.org/whl/xpu')
install(os.environ.get('OPENVINO_COMMAND', 'openvino==2024.6.0'), 'openvino', ignore=True)
install('nncf==2.7.0', ignore=True, no_deps=True) # requires older pandas
ts('ipex', t_start)
return torch_command
@@ -730,12 +737,12 @@ def install_openvino(torch_command):
check_python(supported_minors=[9, 10, 11, 12], reason='OpenVINO backend requires a Python version between 3.9 and 3.12')
log.info('OpenVINO: selected')
if sys.platform == 'darwin':
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.3.1 torchvision==0.18.1')
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.6.0 torchvision==0.21.0')
else:
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.3.1+cpu torchvision==0.18.1+cpu --index-url https://download.pytorch.org/whl/cpu')
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.6.0+cpu torchvision==0.21.0+cpu --index-url https://download.pytorch.org/whl/cpu')
install(os.environ.get('OPENVINO_COMMAND', 'openvino==2024.6.0'), 'openvino')
install('nncf==2.14.1', 'nncf')
install(os.environ.get('OPENVINO_COMMAND', 'openvino==2025.0.0'), 'openvino')
install(os.environ.get('NNCF_COMMAND', 'nncf==2.15.0'), 'nncf')
os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX')
if os.environ.get("NEOReadDebugKeys", None) is None:
os.environ.setdefault('NEOReadDebugKeys', '1')
@@ -824,14 +831,12 @@ def check_torch():
torch_command = install_ipex(torch_command)
elif allow_openvino and args.use_openvino: # prioritize openvino
torch_command = install_openvino(torch_command)
elif is_cuda_available:
torch_command = install_cuda()
elif is_rocm_available:
torch_command = install_rocm_zluda()
elif is_ipex_available:
torch_command = install_ipex(torch_command)
else:
machine = platform.machine()
if sys.platform == 'darwin':
@@ -1074,6 +1079,14 @@ def install_submodules(force=True):
return '\n'.join(res)
def reload(package):
modules = [m for m in sys.modules if m.startswith(package)]
for m in modules:
del sys.modules[m]
sys.modules[package] = importlib.import_module(package)
log.debug(f'Reload: package={package} version={sys.modules[package].__version__ if hasattr(sys.modules[package], "__version__") else "N/A"}')
def ensure_base_requirements():
t_start = time.time()
setuptools_version = '69.5.1'
@@ -1121,7 +1134,10 @@ def install_optional():
install('pynvml', ignore=True)
install('ultralytics==8.3.40', ignore=True)
install('Cython', ignore=True)
install('insightface', ignore=True) # problematic build
install('insightface==0.7.3', ignore=True) # problematic build
install('albumentations==1.4.3', ignore=True)
install('pydantic==1.10.21', ignore=True)
reload('pydantic')
install('nncf==2.7.0', ignore=True, no_deps=True) # requires older pandas
# install('flash-attn', ignore=True) # requires cuda and nvcc to be installed
install('gguf', ignore=True)
@@ -1308,7 +1324,7 @@ def check_venv():
t_start = time.time()
import site
pkg_path = [try_relpath(p) for p in site.getsitepackages() if os.path.exists(p)]
log.debug(f'Packages: venv={try_relpath(sys.prefix)} site={pkg_path}')
log.debug(f'Packages: prefix={try_relpath(sys.prefix)} site={pkg_path}')
for p in pkg_path:
invalid = []
for f in os.listdir(p):
+5 -4
View File
@@ -294,7 +294,7 @@ function selectHistory(id) {
const headers = new Headers();
headers.set('Content-Type', 'application/json');
const init = { method: 'POST', body: { name: id }, headers };
fetch('/sdapi/v1/history', { method: 'POST', body: JSON.stringify({ name: id }), headers });
fetch(`${window.api}/history`, { method: 'POST', body: JSON.stringify({ name: id }), headers });
}
let enDirty = false;
@@ -464,13 +464,13 @@ function setupExtraNetworksForTab(tabname) {
en.style.position = 'absolute';
en.style.height = 'auto';
en.style.width = `${window.opts.extra_networks_sidebar_width}vw`;
en.style.maxWidth = '655px';
en.style.maxWidth = '50vw';
en.style.right = '0';
en.style.top = '13em';
en.style.transition = 'width 0.3s ease';
en.style.zIndex = 100;
// gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = `${100 - 2 - window.opts.extra_networks_sidebar_width}vw`;
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = `calc(100vw - 2em - min(${window.opts.extra_networks_sidebar_width}vw, 655px))`;
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = `calc(100vw - 2em - min(${window.opts.extra_networks_sidebar_width}vw, 50vw))`;
} else {
en.style.position = 'relative';
en.style.height = 'unset';
@@ -515,5 +515,6 @@ async function setupExtraNetworks() {
registerPrompt('img2img', 'img2img_neg_prompt');
registerPrompt('control', 'control_prompt');
registerPrompt('control', 'control_neg_prompt');
log('initNetworks');
log('initNetworks', window.opts.extra_networks_card_size);
document.documentElement.style.setProperty('--card-size', `${window.opts.extra_networks_card_size}px`);
}
+17 -16
View File
@@ -23,19 +23,20 @@ class GalleryFolder extends HTMLElement {
}
connectedCallback() {
const style = document.createElement('style');
style.textContent = `
.gallery-folder {
cursor: pointer;
padding: 8px 6px 8px 6px;
}
.gallery-folder:hover {
background-color: var(--button-primary-background-fill-hover);
}
.gallery-folder-selected {
background-color: var(--button-primary-background-fill);
}
`;
const style = document.createElement('style'); // silly but necessasry since we're inside shadowdom
if (window.opts.theme_type === 'Modern') {
style.textContent = `
.gallery-folder { cursor: pointer; padding: 8px 6px 8px 6px; background-color: var(--sd-secondary-color); }
.gallery-folder:hover { background-color: var(--button-primary-background-fill-hover); }
.gallery-folder-selected { background-color: var(--sd-button-selected-color); color: var(--sd-button-selected-text-color); }
`;
} else {
style.textContent = `
.gallery-folder { cursor: pointer; padding: 8px 6px 8px 6px; }
.gallery-folder:hover { background-color: var(--button-primary-background-fill-hover); }
.gallery-folder-selected { background-color: var(--button-primary-background-fill); }
`;
}
this.shadow.appendChild(style);
const div = document.createElement('div');
div.className = 'gallery-folder';
@@ -92,7 +93,7 @@ async function addSeparators() {
async function delayFetchThumb(fn) {
while (outstanding > 16) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return
outstanding++;
const res = await fetch(`/sdapi/v1/browser/thumb?file=${encodeURI(fn)}`, { priority: 'low' });
const res = await fetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}`, { priority: 'low' });
if (!res.ok) {
error(`fetchThumb: ${res.statusText}`);
outstanding--;
@@ -334,7 +335,7 @@ async function fetchFilesHT(evt) {
el.status.innerText = `Folder | ${evt.target.name} | in-progress`;
let numFiles = 0;
const res = await fetch(`/sdapi/v1/browser/files?folder=${encodeURI(evt.target.name)}`);
const res = await fetch(`${window.api}/browser/files?folder=${encodeURI(evt.target.name)}`);
if (!res || res.status !== 200) {
el.status.innerText = `Folder | ${evt.target.name} | failed: ${res?.statusText}`;
return;
@@ -412,7 +413,7 @@ async function pruneImages() {
async function galleryVisible() {
// if (el.folders.children.length > 0) return;
const res = await fetch('/sdapi/v1/browser/folders');
const res = await fetch(`${window.api}/browser/folders`);
if (!res || res.status !== 200) return;
el.folders.innerHTML = '';
url = res.url.split('/sdapi')[0].replace('http', 'ws'); // update global url as ws need fqdn
+2 -2
View File
@@ -37,7 +37,7 @@ async function createSplash() {
await preloadImages();
const imgEl = `<div id="spash-img" class="splash-img" alt="logo" style="background-image: url(file=html/logo-bg-${dark ? 'dark' : 'light'}.jpg), url(file=html/logo-bg-${num}.jpg); background-blend-mode: ${dark ? 'multiply' : 'lighten'}"></div>`;
document.getElementById('splash').insertAdjacentHTML('afterbegin', imgEl);
fetch('/sdapi/v1/motd')
fetch(`${window.api}/motd`)
.then((res) => res.text())
.then((text) => {
const motdEl = document.getElementById('motd');
@@ -52,7 +52,7 @@ async function removeSplash() {
log('removeSplash');
const t = Math.round(performance.now() - appStartTime) / 1000;
log('startupTime', t);
xhrPost('/sdapi/v1/log', { message: `ready time=${t}` });
xhrPost(`${window.api}/log`, { message: `ready time=${t}` });
}
window.onload = createSplash;
+10 -6
View File
@@ -38,14 +38,18 @@ async function logMonitor() {
while (logMonitorEl.childElementCount > 100) logMonitorEl.removeChild(logMonitorEl.firstChild);
if (atBottom) logMonitorEl.scrollTop = logMonitorEl.scrollHeight;
else logMonitorEl.parentElement.style = 'border-bottom: 2px solid var(--highlight-color);';
document.getElementById('logWarnings').innerText = logWarnings;
document.getElementById('logErrors').innerText = logErrors;
const elWarn = document.getElementById('logWarnings');
const elErr = document.getElementById('logErrors');
const modenUIBtn = document.getElementById('btn_console');
if (elWarn) elWarn.innerText = logWarnings;
if (elErr) elErr.innerText = logErrors;
if (modenUIBtn) modenUIBtn.setAttribute('error-count', logErrors > 0 ? logErrors : '');
};
document.getElementById('txt2img_gallery').style.height = opts.logmonitor_show ? '50vh' : '55vh';
document.getElementById('img2img_gallery').style.height = opts.logmonitor_show ? '50vh' : '55vh';
const txtGallery = document.getElementById('txt2img_gallery');
if (txtGallery) txtGallery.style.height = opts.logmonitor_show ? '50vh' : '55vh';
const imgGallery = document.getElementById('img2img_gallery');
if (imgGallery) imgGallery.style.height = opts.logmonitor_show ? '50vh' : '55vh';
if (!opts.logmonitor_show) {
Array.from(document.getElementsByClassName('log-monitor')).forEach((el) => el.style.display = 'none');
@@ -66,7 +70,7 @@ async function logMonitor() {
if (!logMonitorEl) return;
const atBottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight);
try {
const res = await fetch('/sdapi/v1/log?clear=True');
const res = await fetch(`${window.api}/log?clear=True`);
if (res?.ok) {
logMonitorStatus = true;
const lines = await res.json();
@@ -112,7 +116,7 @@ async function initLogMonitor() {
</table>
`;
el.style.display = 'none';
fetch(`/sdapi/v1/start?agent=${encodeURI(navigator.userAgent)}`);
fetch(`${window.api}/start?agent=${encodeURI(navigator.userAgent)}`);
logMonitor();
log('initLogMonitor');
}
+20 -8
View File
@@ -1,29 +1,41 @@
const timeout = 30000;
const scrollBottom = async (el) => {
const lastChild = el.lastElementChild;
if (lastChild) lastChild.scrollIntoView({ behavior: 'smooth' });
};
const log = async (...msg) => {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
if (window.logger) {
window.logger.innerHTML += window.logPrettyPrint(...msg);
scrollBottom(window.logger);
}
console.log(ts, ...msg); // eslint-disable-line no-console
};
const debug = async (...msg) => {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
if (window.logger) {
window.logger.innerHTML += window.logPrettyPrint(...msg);
scrollBottom(window.logger);
}
console.debug(ts, ...msg); // eslint-disable-line no-console
};
const error = async (...msg) => {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
if (window.logger) {
window.logger.innerHTML += window.logPrettyPrint(...msg);
scrollBottom(window.logger);
}
console.error(ts, ...msg); // eslint-disable-line no-console
// const txt = msg.join(' ');
// if (!txt.includes('asctime') && !txt.includes('xhr.')) xhrPost('/sdapi/v1/log', { error: txt }); // eslint-disable-line no-use-before-define
};
const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = timeout) => {
const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = opts.ui_request_timeout || 30000) => {
const err = (msg) => {
if (!ignore) {
error(`${msg}: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
@@ -32,7 +44,7 @@ const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined
};
xhrObj.setRequestHeader('Content-Type', 'application/json');
xhrObj.timeout = timeout;
xhrObj.timeout = opts.ui_request_timeout || 30000;
xhrObj.ontimeout = () => err('xhr.ontimeout');
xhrObj.onerror = () => err('xhr.onerror');
xhrObj.onabort = () => err('xhr.onabort');
@@ -54,14 +66,14 @@ const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined
xhrObj.send(req);
};
const xhrGet = (url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = timeout) => {
const xhrGet = (url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = opts.ui_request_timeout || 30000) => {
const xhr = new XMLHttpRequest();
const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
xhr.open('GET', `${url}?${args}`, true);
xhrInternal(xhr, data, handler, errorHandler, ignore, serverTimeout);
};
function xhrPost(url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = timeout) {
function xhrPost(url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = opts.ui_request_timeout || 30000) {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhrInternal(xhr, data, handler, errorHandler, ignore, serverTimeout);
+4 -4
View File
@@ -25,7 +25,7 @@ const loginHTML = `
function forceLogin() {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/login';
form.action = `${location.href}login`;
form.id = 'loginForm';
form.style.cssText = loginCSS;
form.innerHTML = loginHTML;
@@ -39,8 +39,8 @@ function forceLogin() {
const formData = new FormData(form);
formData.append('username', username.value);
formData.append('password', password.value);
console.warn('login', formData);
fetch('/login', {
console.warn('login', location.href, formData);
fetch(`${location.href}login`, {
method: 'POST',
body: formData,
})
@@ -59,7 +59,7 @@ function forceLogin() {
}
function loginCheck() {
fetch('/login_check', {})
fetch(`${location.href}login_check`, {})
.then((res) => {
if (res.status === 200) console.log('login ok');
else forceLogin();
+1 -1
View File
@@ -30,7 +30,7 @@ async function updateNVMLChart(mem, load) {
async function updateNVML() {
try {
const res = await fetch('/sdapi/v1/nvml');
const res = await fetch(`${window.api}/nvml`);
if (!res.ok) {
clearInterval(nvmlInterval);
nvmlEl.style.display = 'none';
+2
View File
@@ -6,6 +6,7 @@
--color-info: #D4D4D4;
--color-warning: #FF9900;
--color-error: #BE0000;
--card-size: 160px;
}
a { font-weight: bold; cursor: pointer; }
h2 { margin-top: 1em !important; font-size: var(--text-xxl) !important; }
@@ -132,6 +133,7 @@ div#extras_scale_to_tab div.form { flex-direction: row; }
width: 22em; min-height: 1.3em; font-size: var(--text-xs); transition: opacity 0.2s ease-in; pointer-events: none; opacity: 0; z-index: 999; }
.tooltip-show { opacity: 0.9; }
.toolbutton-selected { background: var(--background-fill-primary) !important; }
.locale { position: fixed; top: 0.5em; right: 0.5em; background-color: var(--input-background-fill); color: var(--body-text-color); padding: 0.1em; width: 1.2em; height: 1.2em; cursor: pointer; font-size: 0.8em; font-weight: 800; font-family: monospace; opacity: 50%; }
#txt2img_hdr_color_row > div { min-width: unset !important; max-width: unset !important; }
#txt2img_advanced_options, #img2img_advanced_options, #control_advanced_options { min-width: 100%; }
#txt2img_advanced_options .gradio-checkbox, #img2img_advanced_options .gradio-checkbox, #control_advanced_options .gradio-checkbox { min-width: unset !important; max-width: fit-content; }
+137 -37
View File
@@ -1,17 +1,44 @@
const allLocales = ['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'pt', 'hr', 'ru', 'zh'];
const localeData = {
prev: null,
locale: null,
data: [],
timeout: null,
finished: false,
initial: true,
type: 2,
el: null,
hint: null,
btn: null,
};
async function cycleLocale() {
log('cycleLocale', localeData.prev, localeData.locale);
const index = allLocales.indexOf(localeData.prev);
localeData.locale = allLocales[(index + 1) % allLocales.length];
localeData.btn.innerText = localeData.locale;
// localeData.btn.style.backgroundColor = localeData.locale !== 'en' ? 'var(--primary-500)' : '';
localeData.finished = false;
localeData.data = [];
localeData.prev = localeData.locale;
window.opts.ui_locale = localeData.locale;
await setHints(); // eslint-disable-line no-use-before-define
}
async function tooltipCreate() {
localeData.el = document.createElement('div');
localeData.el.className = 'tooltip';
localeData.el.id = 'tooltip-container';
localeData.el.innerText = 'this is a hint';
gradioApp().appendChild(localeData.el);
localeData.hint = document.createElement('div');
localeData.hint.className = 'tooltip';
localeData.hint.id = 'tooltip-container';
localeData.hint.innerText = 'this is a hint';
gradioApp().appendChild(localeData.hint);
localeData.btn = gradioApp().getElementById('locale-container');
if (!localeData.btn) {
localeData.btn = document.createElement('div');
localeData.btn.className = 'locale';
localeData.btn.id = 'locale-container';
gradioApp().appendChild(localeData.btn);
}
localeData.btn.innerText = localeData.locale;
localeData.btn.onclick = cycleLocale;
if (window.opts.tooltips === 'None') localeData.type = 0;
if (window.opts.tooltips === 'Browser default') localeData.type = 1;
if (window.opts.tooltips === 'UI tooltips') localeData.type = 2;
@@ -19,31 +46,56 @@ async function tooltipCreate() {
async function tooltipShow(e) {
if (e.target.dataset.hint) {
localeData.el.classList.add('tooltip-show');
localeData.el.innerHTML = `<b>${e.target.textContent}</b><br>${e.target.dataset.hint}`;
localeData.hint.classList.add('tooltip-show');
localeData.hint.innerHTML = `<b>${e.target.textContent}</b><br>${e.target.dataset.hint}`;
if (e.clientX > window.innerWidth / 2) {
localeData.el.classList.add('tooltip-left');
localeData.hint.classList.add('tooltip-left');
} else {
localeData.el.classList.remove('tooltip-left');
localeData.hint.classList.remove('tooltip-left');
}
}
}
async function tooltipHide(e) {
localeData.el.classList.remove('tooltip-show');
localeData.hint.classList.remove('tooltip-show');
}
async function validateHints(elements, data) {
let original = elements.map((e) => e.textContent.toLowerCase().trim()).sort((a, b) => a > b);
async function validateHints(json, elements) {
json.missing = [];
const data = Object.values(json).flat().filter((e) => e.hint.length > 0);
for (const e of data) e.label = e.label.trim();
let original = elements.map((e) => e.textContent.toLowerCase().trim()).sort(); // should be case sensitive
let duplicateUI = original.filter((e, i, a) => a.indexOf(e.toLowerCase()) !== i).sort();
original = [...new Set(original)]; // remove duplicates
const current = data.map((e) => e.label.toLowerCase().trim()).sort((a, b) => a > b);
duplicateUI = [...new Set(duplicateUI)]; // remove duplicates
const current = data.map((e) => e.label.toLowerCase().trim()).sort(); // should be case sensitive
log('all elements:', original);
log('all hints:', current);
log('hints-differences', { elements: original.length, hints: current.length });
const missingLocale = original.filter((e) => !current.includes(e));
log('missing in locale:', missingLocale);
const missingUI = current.filter((e) => !original.includes(e));
log('in locale but not ui:', missingUI);
const missingHints = original.filter((e) => !current.includes(e.toLowerCase())).sort();
const orphanedHints = current.filter((e) => !original.includes(e.toLowerCase())).sort();
const duplicateHints = current.filter((e, i, a) => a.indexOf(e.toLowerCase()) !== i).sort();
log('duplicate hints:', duplicateHints);
log('duplicate labels:', duplicateUI);
return [missingHints, orphanedHints];
}
async function addMissingHints(json, missingHints) {
if (missingHints.length === 0) return;
json.missing = [];
for (const h of missingHints.sort()) {
if (h.length <= 1) continue;
json.missing.push({ id: '', label: h, localized: '', hint: h });
}
log('missing hints', missingHints);
log('added missing hints:', { missing: json.missing });
}
async function removeOrphanedHints(json, orphanedHints) {
const data = Object.values(json).flat().filter((e) => e.hint.length > 0);
for (const e of data) e.label = e.label.trim();
const orphaned = data.filter((e) => orphanedHints.includes(e.label.toLowerCase()));
log('orphaned hints:', { orphaned });
}
async function replaceButtonText(el) {
@@ -65,30 +117,72 @@ async function replaceButtonText(el) {
}
}
async function setHints() {
if (localeData.finished) return;
if (localeData.data.length === 0) {
const res = await fetch('/file=html/locale_en.json');
const json = await res.json();
localeData.data = Object.values(json).flat().filter((e) => e.hint.length > 0);
for (const e of localeData.data) e.label = e.label.toLowerCase().trim();
async function getLocaleData(desiredLocale = null) {
if (desiredLocale) desiredLocale = desiredLocale.split(':')[0];
if (desiredLocale === 'Auto') {
try {
localeData.locale = navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language;
localeData.locale = localeData.locale.split('-')[0];
localeData.prev = localeData.locale;
} catch (e) {
localeData.locale = 'en';
log('getLocale', e);
}
} else {
localeData.locale = desiredLocale || 'en';
localeData.prev = localeData.locale;
}
log('getLocale', desiredLocale, localeData.locale);
// primary
let json = {};
try {
let res = await fetch(`${window.subpath}/file=html/locale_${localeData.locale}.json`);
if (!res || !res.ok) {
localeData.locale = 'en';
res = await fetch(`${window.subpath}/file=html/locale_${localeData.locale}.json`);
}
json = await res.json();
} catch { /**/ }
try {
const res = await fetch(`${window.subpath}/file=html/override_${localeData.locale}.json`);
if (res && res.ok) json.override = await res.json();
} catch { /**/ }
return json;
}
async function setHints(analyze = false) {
let json = {};
let overrideData = [];
if (localeData.finished) return;
if (Object.keys(opts).length === 0) return;
const elements = [
...Array.from(gradioApp().querySelectorAll('button')),
...Array.from(gradioApp().querySelectorAll('label > span')),
...Array.from(gradioApp().querySelectorAll('.label-wrap > span')),
];
if (elements.length === 0) return;
if (Object.keys(opts).length === 0) return;
if (!localeData.el) tooltipCreate();
if (localeData.data.length === 0) {
json = await getLocaleData(window.opts.ui_locale);
overrideData = Object.values(json.override || {}).flat().filter((e) => e.hint.length > 0);
const jsonData = Object.values(json).flat().filter((e) => e.hint.length > 0);
localeData.data = [...overrideData, ...jsonData];
}
if (!localeData.hint) tooltipCreate();
let localized = 0;
let hints = 0;
localeData.finished = true;
const t0 = performance.now();
for (const el of elements) {
const found = localeData.data.find((l) => l.label === el.textContent.toLowerCase().trim());
let found;
if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
if (found?.localized?.length > 0) {
if (!el.dataset.original) el.dataset.original = el.textContent;
localized++;
el.textContent = found.localized;
} else if (found?.label && !localeData.initial && (localeData.locale === 'en')) { // reset to english
el.textContent = found.label;
}
// replaceButtonText(el);
if (found?.hint?.length > 0) {
@@ -104,15 +198,21 @@ async function setHints() {
}
}
}
localeData.finished = true;
localeData.initial = false;
const t1 = performance.now();
log('setHints', { type: localeData.type, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 });
// localeData.btn.style.backgroundColor = localeData.locale !== 'en' ? 'var(--primary-500)' : '';
log('setHints', { type: localeData.type, locale: localeData.locale, elements: elements.length, localized, hints, data: localeData.data.length, override: overrideData.length, time: Math.round(t1 - t0) });
// sortUIElements();
// validateHints(elements, localeData.data);
if (analyze) {
const [missingHints, orphanedHints] = await validateHints(json, elements);
await addMissingHints(json, missingHints);
await removeOrphanedHints(json, orphanedHints);
}
}
/*
onAfterUiUpdate(async () => {
if (localeData.timeout) clearTimeout(localeData.timeout);
localeData.timeout = setTimeout(setHints, 250);
});
*/
const analyzeHints = async () => {
localeData.finished = false;
localeData.data = [];
await setHints(true);
};
+5 -3
View File
@@ -117,8 +117,10 @@ onAfterUiUpdate(async () => {
});
const settingsSearch = gradioApp().querySelectorAll('#settings_search > label > textarea')[0];
let settingsTimer;
settingsSearch.oninput = (e) => {
setTimeout(() => {
if (settingsTimer) clearTimeout(settingsTimer);
settingsTimer = setTimeout(() => {
log('settingsSearch', e.target.value);
showAllSettings();
getSettingsTabs().forEach((section) => {
@@ -128,7 +130,7 @@ onAfterUiUpdate(async () => {
else setting.style.removeProperty('display');
});
});
}, 50);
}, 250);
};
});
@@ -150,7 +152,7 @@ async function initModels() {
const el = gradioApp().getElementById('main_info');
const en = gradioApp().getElementById('txt2img_extra_networks');
if (!el || !en) return;
const req = await fetch('/sdapi/v1/sd-models');
const req = await fetch(`${window.api}/sd-models`);
const res = req.ok ? await req.json() : [];
log('initModels', res.length);
const ready = () => `
+7
View File
@@ -1,4 +1,6 @@
/* eslint-disable no-undef */
window.api = '/sdapi/v1';
window.subpath = '';
async function initStartup() {
log('initStartup');
@@ -23,6 +25,11 @@ async function initStartup() {
// make sure all of the ui is ready and options are loaded
while (Object.keys(window.opts).length === 0) await sleep(50);
log('mountURL', window.opts.subpath);
if (window.opts.subpath?.length > 0) {
window.subpath = window.opts.subpath;
window.api = `${window.subpath}/sdapi/v1`;
}
executeCallbacks(uiReadyCallbacks);
initLogMonitor();
setupExtraNetworks();
+10 -5
View File
@@ -127,13 +127,13 @@ function switch_to_img2img(...args) {
return Array.from(arguments);
}
function switch_to_sketch(...args) {
function switch_to_inpaint(...args) {
switchToTab('Image');
switch_to_img2img_tab(1);
return Array.from(arguments);
}
function switch_to_inpaint(...args) {
function switch_to_sketch(...args) {
switchToTab('Image');
switch_to_img2img_tab(2);
return Array.from(arguments);
@@ -155,6 +155,11 @@ function switch_to_control(...args) {
return Array.from(arguments);
}
function switch_to_caption(...args) {
switchToTab('Caption');
return Array.from(arguments);
}
function get_tab_index(tabId) {
let res = 0;
gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button')
@@ -385,7 +390,7 @@ function monitorServerStatus() {
<h1>Waiting for server...</h1>
<script>
function monitorServerStatus() {
fetch('/sdapi/v1/progress?skip_current_image=true')
fetch('${window.api}/progress?skip_current_image=true')
.then((res) => { !res?.ok ? setTimeout(monitorServerStatus, 1000) : location.reload(); })
.catch((e) => setTimeout(monitorServerStatus, 1000))
}
@@ -400,7 +405,7 @@ function monitorServerStatus() {
function restartReload() {
document.body.style = 'background: #222222; font-size: 1rem; font-family:monospace; margin-top:20%; color:lightgray; text-align:center';
document.body.innerHTML = '<h1>Server shutdown in progress...</h1>';
fetch('/sdapi/v1/progress?skip_current_image=true')
fetch(`${window.api}/progress?skip_current_image=true`)
.then((res) => setTimeout(restartReload, 1000))
.catch((e) => setTimeout(monitorServerStatus, 500));
return [];
@@ -479,7 +484,7 @@ function toggleCompact(val, old) {
function previewTheme() {
let name = gradioApp().getElementById('setting_gradio_theme').querySelectorAll('input')?.[0].value || '';
fetch('/file=html/themes.json')
fetch(`${window.subpath}/file=html/themes.json`)
.then((res) => {
res.json()
.then((themes) => {
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+9 -6
View File
@@ -32,7 +32,11 @@ class Api:
self.generate = generate.APIGenerate(queue_lock)
self.process = process.APIProcess(queue_lock)
self.control = control.APIControl(queue_lock)
# compatibility api
self.text2imgapi = self.generate.post_text2img
self.img2imgapi = self.generate.post_img2img
def register(self):
# server api
self.add_api_route("/sdapi/v1/motd", server.get_motd, methods=["GET"], response_model=str)
self.add_api_route("/sdapi/v1/log", server.get_log, methods=["GET"], response_model=List[str])
@@ -97,16 +101,15 @@ class Api:
self.add_api_route("/sdapi/v1/refresh-loras", endpoints.post_refresh_loras, methods=["POST"])
# gallery api
gallery.register_api(app)
gallery.register_api(self.app)
# compatibility api
self.text2imgapi = self.generate.post_text2img
self.img2imgapi = self.generate.post_img2img
def add_api_route(self, path: str, endpoint, **kwargs):
if (shared.cmd_opts.auth or shared.cmd_opts.auth_file) and shared.cmd_opts.api_only:
return self.app.add_api_route(path, endpoint, dependencies=[Depends(self.auth)], **kwargs)
return self.app.add_api_route(path, endpoint, **kwargs)
kwargs['dependencies'] = [Depends(self.auth)]
if shared.opts.subpath is not None and len(shared.opts.subpath) > 0:
self.app.add_api_route(f'{shared.opts.subpath}{path}', endpoint, **kwargs)
self.app.add_api_route(path, endpoint, **kwargs)
def auth(self, credentials: HTTPBasicCredentials = Depends(HTTPBasic())):
# this is only needed for api-only since otherwise auth is handled in gradio/routes.py
+1 -1
View File
@@ -113,7 +113,7 @@ def post_vqa(req: models.ReqVQA):
image = helpers.decode_base64_to_image(req.image)
image = image.convert('RGB')
from modules.interrogate import vqa
answer = vqa.interrogate(req.question, image, req.model)
answer = vqa.interrogate(req.question, '', image, req.model)
return models.ResVQA(answer=answer)
def post_unload_checkpoint():
+8 -4
View File
@@ -126,7 +126,7 @@ def register_api(app: FastAPI): # register api
shared.log.error(f'Gallery image: file="{filepath}" {e}')
return {}
@app.get('/sdapi/v1/browser/folders', response_model=List[str])
# @app.get('/sdapi/v1/browser/folders', response_model=List[str])
def get_folders():
folders = [shared.opts.data.get(f, '') for f in OPTS_FOLDERS]
folders += list(shared.opts.browser_folders.split(','))
@@ -141,7 +141,7 @@ def register_api(app: FastAPI): # register api
debug(f'Browser folders: {folders}')
return JSONResponse(content=folders)
@app.get("/sdapi/v1/browser/thumb", response_model=dict)
# @app.get("/sdapi/v1/browser/thumb", response_model=dict)
async def get_thumb(file: str):
try:
decoded = unquote(file).replace('%3A', ':')
@@ -154,7 +154,7 @@ def register_api(app: FastAPI): # register api
content = { 'error': str(e) }
return JSONResponse(content=content)
@app.get("/sdapi/v1/browser/files", response_model=list)
# @app.get("/sdapi/v1/browser/files", response_model=list)
async def ht_files(folder: str):
try:
t0 = time.time()
@@ -172,6 +172,10 @@ def register_api(app: FastAPI): # register api
shared.log.error(f'Gallery: {folder} {e}')
return []
shared.api.add_api_route("/sdapi/v1/browser/folders", get_folders, methods=["GET"], response_model=List[str])
shared.api.add_api_route("/sdapi/v1/browser/thumb", get_thumb, methods=["GET"], response_model=dict)
shared.api.add_api_route("/sdapi/v1/browser/files", ht_files, methods=["GET"], response_model=list)
@app.websocket("/sdapi/v1/browser/files")
async def ws_files(ws: WebSocket):
try:
@@ -180,7 +184,7 @@ def register_api(app: FastAPI): # register api
folder = unquote(folder).replace('%3A', ':')
t0 = time.time()
numFiles = 0
files = files_cache.directory_files(folder, recursive=True)
files = files_cache.list_files(folder, recursive=True)
# files = list(files_cache.directory_files(folder, recursive=True))
# files.sort(key=os.path.getmtime)
for f in files:
+2
View File
@@ -71,6 +71,8 @@ def setup_middleware(app: FastAPI, cmd_opts):
}
if err['code'] == 401 and 'file=' in req.url.path: # dont spam with unauth
return JSONResponse(status_code=err['code'], content=jsonable_encoder(err))
if err['code'] == 404 and 'file=html/' in req.url.path: # dont spam with locales
return JSONResponse(status_code=err['code'], content=jsonable_encoder(err))
log.error(f"API error: {req.method}: {req.url} {err}")
+2
View File
@@ -328,6 +328,8 @@ class ReqInterrogate(BaseModel):
clip_model: str = Field(default="", title="CLiP Model", description="The interrogate model used.")
blip_model: str = Field(default="", title="BLiP Model", description="The interrogate model used.")
InterrogateRequest = ReqInterrogate # alias for backwards compatibility
class ResInterrogate(BaseModel):
caption: Optional[str] = Field(default=None, title="Caption", description="The generated caption for the image.")
medium: Optional[str] = Field(default=None, title="Medium", description="Image medium.")
+1 -2
View File
@@ -39,6 +39,7 @@ def main_args():
group_http = parser.add_argument_group('HTTP')
group_http.add_argument('--theme', type=str, default=os.environ.get("SD_THEME", None), help='Override UI theme')
group_http.add_argument('--locale', type=str, default=os.environ.get("SD_LOCALE", None), help='Override UI locale')
group_http.add_argument("--server-name", type=str, default=os.environ.get("SD_SERVERNAME", None), help="Sets hostname of server, default: %(default)s")
group_http.add_argument("--tls-keyfile", type=str, default=os.environ.get("SD_TLSKEYFILE", None), help="Enable TLS and specify key file, default: %(default)s")
group_http.add_argument("--tls-certfile", type=str, default=os.environ.get("SD_TLSCERTFILE", None), help="Enable TLS and specify cert file, default: %(default)s")
@@ -68,7 +69,6 @@ def compatibility_args():
group_compat.add_argument("--ui-settings-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'config.json'))
group_compat.add_argument("--ui-config-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'ui-config.json'))
group_compat.add_argument("--hide-ui-dir-config", action='store_true', help=argparse.SUPPRESS, default=False)
group_compat.add_argument("--theme", type=str, help=argparse.SUPPRESS, default=None)
group_compat.add_argument("--disable-console-progressbars", action='store_true', help=argparse.SUPPRESS, default=True)
group_compat.add_argument("--disable-safe-unpickle", action='store_true', help=argparse.SUPPRESS, default=True)
group_compat.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS)
@@ -88,7 +88,6 @@ def settings_args(opts, args):
group_compat.add_argument("--ui-settings-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'config.json'))
group_compat.add_argument("--ui-config-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'ui-config.json'))
group_compat.add_argument("--hide-ui-dir-config", action='store_true', help=argparse.SUPPRESS, default=False)
group_compat.add_argument("--theme", type=str, help=argparse.SUPPRESS, default=None)
group_compat.add_argument("--disable-console-progressbars", action='store_true', help=argparse.SUPPRESS, default=True)
group_compat.add_argument("--disable-safe-unpickle", action='store_true', help=argparse.SUPPRESS, default=True)
group_compat.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS)
@@ -42,7 +42,7 @@ def load_model(device, model_path, model_type="dpt_large_384", optimize=True, he
network input
"""
if "openvino" in model_type:
from openvino.runtime import Core
from openvino import Core
keep_aspect_ratio = not square
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import time
import numpy as np
from PIL import Image
from modules.shared import log
from installer import log
from modules.errors import display
from modules import devices, images
+7 -2
View File
@@ -611,6 +611,8 @@ def control_run(state: str = '',
elif input_type == 1: # Init image same as control
if 'control_image' in possible:
p.task_args['control_image'] = p.init_images # switch image and control_image
if 'control_mode' in possible:
p.task_args['control_mode'] = getattr(p, 'control_mode', None)
if 'strength' in possible:
p.task_args['strength'] = p.denoising_strength
p.init_images = [p.override or input_image] * len(active_model)
@@ -620,9 +622,14 @@ def control_run(state: str = '',
init_image = input_image
if 'control_image' in possible:
p.task_args['control_image'] = p.init_images # switch image and control_image
if 'control_mode' in possible:
p.task_args['control_mode'] = getattr(p, 'control_mode', None)
if 'strength' in possible:
p.task_args['strength'] = p.denoising_strength
p.init_images = [init_image] * len(active_model)
if hasattr(shared.sd_model, 'controlnet') and hasattr(p.task_args, 'control_image') and len(p.task_args['control_image']) > 1 and (shared.sd_model.__class__.__name__ == 'StableDiffusionXLControlNetUnionPipeline'): # special case for controlnet-union
p.task_args['control_image'] = [[x] for x in p.task_args['control_image']]
p.task_args['control_mode'] = [[x] for x in p.task_args['control_mode']]
if is_generator:
image_txt = f'{blended_image.width}x{blended_image.height}' if blended_image is not None else 'None'
@@ -664,8 +671,6 @@ def control_run(state: str = '',
if unit_type == 'lite':
p.init_image = [input_image]
instance.apply(selected_models, processed_image, control_conditioning)
if getattr(p, 'control_mode', None) is not None:
p.task_args['control_mode'] = getattr(p, 'control_mode', None)
if hasattr(p, 'init_images') and p.init_images is None: # delete empty
del p.init_images
+1 -1
View File
@@ -1,7 +1,7 @@
from typing import Union
from PIL import Image
import gradio as gr
from modules.shared import log
from installer import log
from modules.control import processors
from modules.control.units import controlnet
from modules.control.units import xs
+4 -5
View File
@@ -338,12 +338,11 @@ class ControlNetPipeline():
classes = [c.__class__.__name__ for c in controlnets]
if any(c == 'ControlNetUnionModel' for c in classes):
if not all(c == 'ControlNetUnionModel' for c in classes):
log.warning(f'Control {what}: units={classes} mixed type')
log.warning(f'Control {what}: units={classes} mixed type is not supported')
return
if isinstance(controlnets, list) and len(controlnets) == 1:
controlnets = controlnets[0]
cls = StableDiffusionXLControlNetUnionPipeline
if len(controlnets) > 1:
# TODO controlnet-union multi-unit
log.warning(f'Control {what}: units={classes} supports single unit only')
controlnets = controlnets[0]
else:
cls = StableDiffusionXLControlNetPipeline
self.pipeline = cls(
+1 -1
View File
@@ -3,7 +3,7 @@ import time
from typing import Union
import threading
from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline # pylint: disable=unused-import
from modules.shared import log
from installer import log
from modules import errors, sd_models
from modules.control.units import detect
+4 -4
View File
@@ -36,7 +36,7 @@ def HWC3(x):
def make_noise_disk(H, W, C, F):
noise = np.random.uniform(low=0, high=1, size=((H // F) + 2, (W // F) + 2, C))
noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_CUBIC)
noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_LANCZOS4)
noise = noise[F: F + H, F: F + W]
noise -= np.min(noise)
noise /= np.max(noise)
@@ -77,7 +77,7 @@ def img2mask(img, H, W, low=10, high=90):
y = img[:, :, random.randrange(0, img.shape[2])]
else:
y = img
y = cv2.resize(y, (W, H), interpolation=cv2.INTER_CUBIC)
y = cv2.resize(y, (W, H), interpolation=cv2.INTER_LANCZOS4)
if random.uniform(0, 1) < 0.5:
y = 255 - y
return y < np.percentile(y, random.randrange(low, high))
@@ -92,7 +92,7 @@ def resize_image(input_image, resolution):
W *= k
H = int(np.round(H / 64.0)) * 64
W = int(np.round(W / 64.0)) * 64
img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA)
img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4)
return img
@@ -150,7 +150,7 @@ def blend(images):
y = np.zeros((images[0].shape[0], images[0].shape[1], 3), dtype=np.float32)
for img in images:
if img.shape[0] != y.shape[0] or img.shape[1] != y.shape[1]:
img = cv2.resize(img, (y.shape[1], y.shape[0]), interpolation=cv2.INTER_CUBIC)
img = cv2.resize(img, (y.shape[1], y.shape[0]), interpolation=cv2.INTER_LANCZOS4)
if len(img.shape) == 3 and img.shape[2] == 4: # rgba to rgb
img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
if len(img.shape) == 2: # grayscale to rgb
+49 -37
View File
@@ -6,7 +6,7 @@ from functools import wraps
import torch
from modules import rocm
from modules.errors import log, display, install as install_traceback
from installer import install
from installer import install, installed
debug = os.environ.get('SD_DEVICE_DEBUG', None) is not None
@@ -409,36 +409,57 @@ def set_sdpa_params():
try:
if opts.cross_attention_optimization != "Scaled-Dot-Product":
return
try:
torch.backends.cuda.enable_flash_sdp('Flash attention' in opts.sdp_options)
torch.backends.cuda.enable_mem_efficient_sdp('Memory attention' in opts.sdp_options)
torch.backends.cuda.enable_math_sdp('Math attention' in opts.sdp_options)
except Exception as e:
log.warning(f'Torch attention: {e}')
try:
global sdpa_original # pylint: disable=global-statement
if sdpa_original is not None:
torch.nn.functional.scaled_dot_product_attention = sdpa_original
else:
sdpa_original = torch.nn.functional.scaled_dot_product_attention
except Exception as e:
log.warning(f'Torch SDPA: {e}')
if backend == "rocm":
if 'Flash attention' in opts.sdp_options:
try:
# https://github.com/huggingface/diffusers/discussions/7172
from flash_attn import flash_attn_func
sdpa_pre_flash_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(sdpa_pre_flash_atten)
def sdpa_flash_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
if query.shape[-1] <= 128 and attn_mask is None and query.dtype != torch.float32:
return flash_attn_func(q=query.transpose(1, 2), k=key.transpose(1, 2), v=value.transpose(1, 2), dropout_p=dropout_p, causal=is_causal, softmax_scale=scale).transpose(1, 2)
else:
return sdpa_pre_flash_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
torch.nn.functional.scaled_dot_product_attention = sdpa_flash_atten
log.debug('Torch ROCm Flash Attention')
except Exception as err:
log.error(f'Torch ROCm Flash Attention: {err}')
except Exception as err:
log.warning(f'Torch attention: type="sdpa" {err}')
try:
torch.backends.cuda.enable_flash_sdp('Flash attention' in opts.sdp_options)
torch.backends.cuda.enable_mem_efficient_sdp('Memory attention' in opts.sdp_options)
torch.backends.cuda.enable_math_sdp('Math attention' in opts.sdp_options)
log.debug(f'Torch attention: type="sdpa" flash={"Flash attention" in opts.sdp_options} memory={"Memory attention" in opts.sdp_options} math={"Math attention" in opts.sdp_options}')
except Exception as err:
log.warning(f'Torch attention: type="sdpa" {err}')
# Stack hijcaks in reverse order. This gives priority to the last added hijack.
# If the last hijack is not compatible, it will use the one before it and so on.
if 'Dynamic attention' in opts.sdp_options:
try:
global sdpa_pre_dyanmic_atten # pylint: disable=global-statement
sdpa_pre_dyanmic_atten = torch.nn.functional.scaled_dot_product_attention
from modules.sd_hijack_dynamic_atten import dynamic_scaled_dot_product_attention
torch.nn.functional.scaled_dot_product_attention = dynamic_scaled_dot_product_attention
log.debug('Torch attention: type="dynamic attention"')
except Exception as err:
log.error(f'Torch attention: type="dynamic attention" {err}')
if 'CK Flash attention' in opts.sdp_options:
try:
if backend == "rocm":
if not installed('flash-attn'):
agent = rocm.Agent(getattr(torch.cuda.get_device_properties(device), "gcnArchName", "gfx0000"))
install(rocm.get_flash_attention_command(agent), reinstall=True)
else:
install('flash-attn')
from flash_attn import flash_attn_func
sdpa_pre_flash_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(sdpa_pre_flash_atten)
def sdpa_flash_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
if query.shape[-1] <= 128 and attn_mask is None and query.dtype != torch.float32:
return flash_attn_func(q=query.transpose(1, 2), k=key.transpose(1, 2), v=value.transpose(1, 2), dropout_p=dropout_p, causal=is_causal, softmax_scale=scale).transpose(1, 2)
else:
return sdpa_pre_flash_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
torch.nn.functional.scaled_dot_product_attention = sdpa_flash_atten
log.debug('Torch attention: type="ck flash attention"')
except Exception as err:
log.error(f'Torch attention: type="ck flash attention" {err}')
if 'Sage attention' in opts.sdp_options:
try:
install('sageattention')
@@ -446,23 +467,14 @@ def set_sdpa_params():
sdpa_pre_sage_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(sdpa_pre_sage_atten)
def sdpa_sage_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
if query.shape[-1] in {128, 96, 64} and attn_mask is None and query.dtype != torch.float32:
if (query.shape[-1] in {128, 96, 64}) and (attn_mask is None) and (query.dtype != torch.float32):
return sageattn(q=query, k=key, v=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
else:
return sdpa_pre_sage_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
torch.nn.functional.scaled_dot_product_attention = sdpa_sage_atten
log.debug('Torch SDPA Sage Attention')
log.debug('Torch attention: type="sage attention"')
except Exception as err:
log.error(f'Torch SDPA Sage Attention: {err}')
if 'Dynamic attention' in opts.sdp_options:
try:
global sdpa_pre_dyanmic_atten # pylint: disable=global-statement
sdpa_pre_dyanmic_atten = torch.nn.functional.scaled_dot_product_attention
from modules.sd_hijack_dynamic_atten import dynamic_scaled_dot_product_attention
torch.nn.functional.scaled_dot_product_attention = dynamic_scaled_dot_product_attention
log.debug('Torch SDPA Dynamic Attention')
except Exception as err:
log.error(f'Torch SDPA Dynamic Attention: {err}')
log.error(f'Torch attention: type="sage attention" {err}')
except Exception as e:
log.warning(f'Torch SDPA: {e}')
+1 -1
View File
@@ -1,7 +1,7 @@
import math
import torch
from modules.postprocess.realesrgan_model_arch import RealESRGANer
from modules.shared import log
from installer import log
# DML Solution: Some of contents of output tensor turn to 0 after Extended Slices. Move it to cpu.
+4 -1
View File
@@ -162,7 +162,10 @@ def parse_prompt(prompt):
args = m.group(2)
res[name].append(ExtraNetworkParams(items=args.split(":")))
return ""
prompt = re.sub(re_extra_net, found, prompt)
if isinstance(prompt, list):
prompt = [re.sub(re_extra_net, found, p) for p in prompt]
else:
prompt = re.sub(re_extra_net, found, prompt)
return prompt, res
+5 -4
View File
@@ -10,11 +10,12 @@ instightface_mp = None
def get_app(mp_name, threshold=0.5, resolution=640):
global insightface_app, instightface_mp # pylint: disable=global-statement
from installer import install, installed
from installer import install, installed, reload
if not installed('insightface', reload=False, quiet=True):
install('insightface', 'insightface', ignore=False)
install('albumentations==1.4.3', 'albumentations', ignore=False, reinstall=True)
install('pydantic==1.10.21', 'pydantic', ignore=False, reinstall=True)
install('insightface==0.7.3', ignore=False)
install('albumentations==1.4.3', ignore=False, reinstall=True)
install('pydantic==1.10.21', ignore=False, reinstall=True, force=True)
reload('pydantic')
if not installed('ip_adapter', reload=False, quiet=True):
install('git+https://github.com/tencent-ailab/IP-Adapter.git', 'ip_adapter', ignore=False)
@@ -101,6 +101,8 @@ def create_buttons(tabs_list):
name = 'Process'
elif name == 'control':
name = 'Control'
elif name == 'caption':
name = 'Caption'
buttons[tab] = gr.Button(f"{name}", elem_id=f"{tab}_tab")
return buttons
+5 -3
View File
@@ -32,12 +32,14 @@ def apply(p, model_type):
hidiffusion.switching_threshold_ratio_dict['sdxl_4096']['T2_ratio'] = t2
hidiffusion.switching_threshold_ratio_dict['sdxl_turbo_1024']['T2_ratio'] = t2
p.extra_generation_params['HiDiffusion Ratios'] = f'{shared.opts.hidiffusion_t1}/{shared.opts.hidiffusion_t2}'
hidiffusion.apply_hidiffusion(shared.sd_model, apply_raunet=shared.opts.hidiffusion_raunet, apply_window_attn=shared.opts.hidiffusion_attn, model_type=model_type)
pipe = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model
hidiffusion.apply_hidiffusion(pipe, apply_raunet=shared.opts.hidiffusion_raunet, apply_window_attn=shared.opts.hidiffusion_attn, model_type=model_type, steps=p.steps)
p.extra_generation_params['HiDiffusion'] = f'{shared.opts.hidiffusion_raunet}/{shared.opts.hidiffusion_attn}/{shared.opts.hidiffusion_steps > 0}:{shared.opts.hidiffusion_steps}'
t1 = time.time()
shared.log.debug(f'HiDiffusion apply: raunet={shared.opts.hidiffusion_raunet} attn={shared.opts.hidiffusion_attn} aggressive={shared.opts.hidiffusion_steps > 0}:{shared.opts.hidiffusion_steps} t1={shared.opts.hidiffusion_t1} t2={shared.opts.hidiffusion_t2} time={t1-t0:.2f} type={shared.sd_model_type} width={p.width} height={p.height}')
def unapply():
if hasattr(shared.sd_model, "unet"):
hidiffusion.remove_hidiffusion(shared.sd_model)
pipe = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model
if hasattr(pipe, 'unet'):
hidiffusion.remove_hidiffusion(pipe)
+13 -6
View File
@@ -6,6 +6,7 @@ from diffusers.utils.torch_utils import is_torch_version
from diffusers.pipelines import auto_pipeline
current_steps = 50
def sd15_hidiffusion_key():
modified_key = dict()
modified_key['down_module_key'] = ['down_blocks.0.downsamplers.0.conv']
@@ -163,12 +164,14 @@ def make_diffusers_transformer_block(block_class: Type[torch.nn.Module]) -> Type
widow_size = (math.ceil(H/2), math.ceil(W/2))
if rand_num <= 0.25:
shift_size = (0,0)
if rand_num > 0.25 and rand_num <= 0.5:
elif rand_num > 0.25 and rand_num <= 0.5:
shift_size = (widow_size[0]//4, widow_size[1]//4)
if rand_num > 0.5 and rand_num <= 0.75:
elif rand_num > 0.5 and rand_num <= 0.75:
shift_size = (widow_size[0]//4*2, widow_size[1]//4*2)
if rand_num > 0.75 and rand_num <= 1:
elif rand_num > 0.75 and rand_num <= 1:
shift_size = (widow_size[0]//4*3, widow_size[1]//4*3)
else:
shift_size = (0,0)
norm_hidden_states = window_partition(norm_hidden_states, widow_size, shift_size, H, W)
# 1. Retrieve lora scale.
@@ -261,7 +264,7 @@ def make_diffusers_cross_attn_down_block(block_class: Type[torch.nn.Module]) ->
T1_start = 0
T1_end = 0
T1 = 0 # to avoid confict with sdxl-turbo
max_timestep = 50
max_timestep = current_steps
def forward(
self,
@@ -273,6 +276,8 @@ def make_diffusers_cross_attn_down_block(block_class: Type[torch.nn.Module]) ->
encoder_attention_mask: Optional[torch.FloatTensor] = None,
additional_residuals: Optional[torch.FloatTensor] = None,
) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]:
if not hasattr(self.info['pipeline'], '_num_timesteps'):
self.info['pipeline']._num_timesteps = self.max_timestep # pylint: disable=protected-access
self.max_timestep = self.info['pipeline']._num_timesteps # pylint: disable=protected-access
# self.max_timestep = len(self.info['scheduler'].timesteps)
ori_H, ori_W = self.info['size']
@@ -618,13 +623,15 @@ def apply_hidiffusion(
model: torch.nn.Module,
apply_raunet: bool = True,
apply_window_attn: bool = True,
model_type: str = 'None'):
model_type: str = 'None',
steps: int = 50):
"""
model: diffusers model. We support SD 1.5, 2.1, XL, XL Turbo.
apply_raunet: whether to apply RAU-Net
apply_window_attn: whether to apply MSW-MSA.
"""
global current_steps # pylint: disable=global-statement
current_steps = steps
if hasattr(model, 'controlnet'):
from .hidiffusion_controlnet import make_diffusers_sdxl_contrtolnet_ppl, make_diffusers_unet_2d_condition
make_ppl_fn = make_diffusers_sdxl_contrtolnet_ppl
+16
View File
@@ -26,6 +26,22 @@ except Exception:
pass
def sanitize_filename_part(text, replace_spaces=True):
if text is None:
return None
if replace_spaces:
text = text.replace(' ', '_')
invalid_filename_chars = '#<>:"/\\|?*\n\r\t'
invalid_filename_prefix = ' '
invalid_filename_postfix = ' .'
max_filename_part_length = 64
text = text.translate({ord(x): '_' for x in invalid_filename_chars})
text = text.lstrip(invalid_filename_prefix)[:max_filename_part_length]
text = text.rstrip(invalid_filename_postfix)
return text
def atomically_save_image():
Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes
while True:
+5
View File
@@ -71,6 +71,8 @@ class FilenameGenerator:
self.prompt = prompt
else:
self.prompt = p.prompt if p is not None else ''
if isinstance(self.prompt, list):
self.prompt = ' '.join(self.prompt)
self.image = image
if not grid:
self.batch_number = NOTHING if self.p is None or getattr(self.p, 'batch_size', 1) == 1 else (self.p.batch_index + 1 if hasattr(self.p, 'batch_index') else NOTHING)
@@ -205,6 +207,8 @@ class FilenameGenerator:
break
pattern, arg = m.groups()
pattern_args.insert(0, arg)
if isinstance(pattern, list):
pattern = ' '.join(pattern)
fun = self.replacements.get(pattern.lower(), None)
if fun is not None:
try:
@@ -212,6 +216,7 @@ class FilenameGenerator:
replacement = fun(self, *pattern_args)
except Exception as e:
replacement = None
errors.display(e, 'Filename apply pattern')
shared.log.error(f'Filename apply pattern: {x} {e}')
if replacement == NOTHING:
continue
+3
View File
@@ -90,6 +90,9 @@ def parse(infotext):
# debug(f'Negative: {negative}')
params = dict(re_param.findall(remaining))
if len(list(params)) == 0:
params['Prompt'] = infotext
return params
params['Prompt'] = prompt
params['Negative prompt'] = negative
for key, val in params.copy().items():
+1
View File
@@ -189,6 +189,7 @@ def ipex_init(): # pylint: disable=too-many-statements
torch._C._XpuDeviceProperties.minor = 1
# Fix functions with ipex:
# torch.xpu.mem_get_info always returns the total memory as free memory
torch.xpu.mem_get_info = lambda device=None: [(torch.xpu.get_device_properties(device).total_memory - torch.xpu.memory_reserved(device)), torch.xpu.get_device_properties(device).total_memory]
torch.cuda.mem_get_info = torch.xpu.mem_get_info
torch._utils._get_available_device_type = lambda: "xpu"
+30 -8
View File
@@ -3,10 +3,10 @@ import sys
import torch
import nncf
from openvino.frontend import FrontEndManager
from openvino.frontend.pytorch.fx_decoder import TorchFXPythonDecoder
from openvino.frontend.pytorch.torchdynamo.partition import Partitioner
from openvino.runtime import Core, Type, PartialShape, serialize
from openvino.frontend.pytorch.fx_decoder import TorchFXPythonDecoder
from openvino.frontend import FrontEndManager
from openvino import Core, Type, PartialShape, serialize
from openvino.properties import hint as ov_hints
from torch._dynamo.backends.common import fake_tensor_unsupported
@@ -23,6 +23,23 @@ import functools
from modules import shared, devices, sd_models
# importing openvino.runtime forces DeprecationWarning to "always"
# And Intel's own libs (NNCF) imports the deprecated module
# Reset the warnings back to ignore:
try:
import warnings
import openvino.runtime # pylint: disable=unused-import
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
except Exception:
pass
torch._dynamo.config.cache_size_limit = 64 # pylint: disable=protected-access
torch._dynamo.eval_frame.check_if_dynamo_supported = lambda: True # pylint: disable=protected-access
if hasattr(torch._dynamo.config, "inline_inbuilt_nn_modules"):
torch._dynamo.config.inline_inbuilt_nn_modules = False # pylint: disable=protected-access
DEFAULT_OPENVINO_PYTHON_CONFIG = MappingProxyType(
{
"use_python_fusion_cache": True,
@@ -114,9 +131,9 @@ def cached_model_name(model_hash_str, device, args, cache_root, reversed = False
for input_data in args:
if isinstance(input_data, torch.SymInt):
if reversed:
inputs_str = "_" + "torch.SymInt1" + inputs_str
inputs_str = "_" + "torch.SymInt[]" + inputs_str
else:
inputs_str += "_" + "torch.SymInt1"
inputs_str += "_" + "torch.SymInt[]"
elif isinstance(input_data, int):
pass
else:
@@ -176,7 +193,7 @@ def openvino_compile(gm: GraphModule, *example_inputs, model_hash_str: str = Non
for input_data in example_inputs:
if isinstance(input_data, torch.SymInt):
input_types.append(torch.SymInt)
input_shapes.append(torch.Size([1]))
input_shapes.append(torch.Size([]))
elif isinstance(input_data, int):
pass
else:
@@ -426,9 +443,8 @@ def get_subgraph_type(tensor):
return tensor
@register_backend
@fake_tensor_unsupported
def openvino_fx(subgraph, example_inputs):
def openvino_fx(subgraph, example_inputs, options=None):
global dont_use_4bit_nncf
global dont_use_nncf
global dont_use_quant
@@ -528,6 +544,8 @@ def openvino_fx(subgraph, example_inputs):
for node in model.graph.nodes:
if node.target == torch.ops.aten.mul_.Tensor:
node.target = torch.ops.aten.mul.Tensor
elif node.target == torch.ops.aten._unsafe_index.Tensor:
node.target = torch.ops.aten.index.Tensor
with devices.inference_context():
model.eval()
partitioner = Partitioner(options=None)
@@ -543,3 +561,7 @@ def openvino_fx(subgraph, example_inputs):
res = execute(compiled_model, *args, executor="openvino", executor_parameters=executor_parameters, file_name=maybe_fs_cached_name)
return res
return _call
if "openvino_fx" not in torch.compiler.list_backends():
register_backend(compiler_fn=openvino_fx, name="openvino_fx")
+164
View File
@@ -0,0 +1,164 @@
# converted from <https://github.com/city96/SD-Latent-Interposer>
import os
import time
import torch
import torch.nn as nn
from safetensors.torch import load_file
# v1 = Stable Diffusion 1.x
# xl = Stable Diffusion Extra Large (SDXL)
# v3 = Stable Diffusion Version Three (SD3)
# fx = Black Forest Labs Flux dot One
# cc = Stable Cascade (Stage C) [not used]
# ca = Stable Cascade (Stage A/B)
config = {
"v1-to-xl": {"ch_in": 4, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"v1-to-v3": {"ch_in": 4, "ch_out":16, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"xl-to-v1": {"ch_in": 4, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"xl-to-v3": {"ch_in": 4, "ch_out":16, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"v3-to-v1": {"ch_in":16, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"v3-to-xl": {"ch_in":16, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"fx-to-v1": {"ch_in":16, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"fx-to-xl": {"ch_in":16, "ch_out": 4, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"fx-to-v3": {"ch_in":16, "ch_out":16, "ch_mid": 64, "scale": 1.0, "blocks": 12},
"ca-to-v1": {"ch_in": 4, "ch_out": 4, "ch_mid": 64, "scale": 0.5, "blocks": 12},
"ca-to-xl": {"ch_in": 4, "ch_out": 4, "ch_mid": 64, "scale": 0.5, "blocks": 12},
"ca-to-v3": {"ch_in": 4, "ch_out":16, "ch_mid": 64, "scale": 0.5, "blocks": 12},
}
class ResBlock(nn.Module):
"""Block with residuals"""
def __init__(self, ch):
super().__init__()
self.join = nn.ReLU()
self.norm = nn.BatchNorm2d(ch)
self.long = nn.Sequential(
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
nn.SiLU(),
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
nn.SiLU(),
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
nn.Dropout(0.1)
)
def forward(self, x):
x = self.norm(x)
return self.join(self.long(x) + x)
class ExtractBlock(nn.Module):
"""Increase no. of channels by [out/in]"""
def __init__(self, ch_in, ch_out):
super().__init__()
self.join = nn.ReLU()
self.short = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1)
self.long = nn.Sequential(
nn.Conv2d( ch_in, ch_out, kernel_size=3, stride=1, padding=1),
nn.SiLU(),
nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1),
nn.SiLU(),
nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1),
nn.Dropout(0.1)
)
def forward(self, x):
return self.join(self.long(x) + self.short(x))
class InterposerModel(nn.Module):
"""
NN layout, ported from:
https://github.com/city96/SD-Latent-Interposer/blob/main/interposer.py
"""
def __init__(self, ch_in=4, ch_out=4, ch_mid=64, scale=1.0, blocks=12):
super().__init__()
self.ch_in = ch_in
self.ch_out = ch_out
self.ch_mid = ch_mid
self.blocks = blocks
self.scale = scale
self.head = ExtractBlock(self.ch_in, self.ch_mid)
self.core = nn.Sequential(
nn.Upsample(scale_factor=self.scale, mode="nearest"),
*[ResBlock(self.ch_mid) for _ in range(blocks)],
nn.BatchNorm2d(self.ch_mid),
nn.SiLU(),
)
self.tail = nn.Conv2d(self.ch_mid, self.ch_out, kernel_size=3, stride=1, padding=1)
def forward(self, x):
y = self.head(x)
z = self.core(y)
return self.tail(z)
def map_model_name(name: str):
if name == 'sd':
return 'v1'
if name == 'sdxl':
return 'xl'
if name == 'sd3':
return 'v3'
if name == 'f1':
return 'fx'
return name
class Interposer:
def __init__(self):
self.version = 4.0 # network revision
self.loaded = None # current model name
self.model = None # current model
self.vae = None # current VAE
def convert(self, src: str, dst: str, latents: torch.Tensor):
from diffusers import AutoencoderKL
from huggingface_hub import hf_hub_download
from modules import shared, devices
src = map_model_name(src)
dst = map_model_name(dst)
if src == dst:
return None
model_name = f"{src}-to-{dst}"
if model_name not in config:
shared.log.error(f'Interposer: model="{model_name}" unknown')
return None
if (self.loaded != model_name) or (self.model is None):
model_fn = hf_hub_download(
repo_id="city96/SD-Latent-Interposer",
subfolder=f"v{self.version}",
filename=f"{model_name}_interposer-v{self.version}.safetensors",
cache_dir=shared.opts.hfcache_dir,
)
self.model = InterposerModel(**config[model_name])
self.model = self.model.to(device=devices.cpu, dtype=torch.float32)
self.model.eval()
self.model.load_state_dict(load_file(model_fn))
self.loaded = model_name
if dst == 'v1':
vae_repo = 'stable-diffusion-v1-5/stable-diffusion-v1-5'
self.vae = AutoencoderKL.from_pretrained(vae_repo, subfolder='vae', cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype)
elif dst == 'xl':
vae_repo = 'madebyollin/sdxl-vae-fp16-fix'
self.vae = AutoencoderKL.from_pretrained(vae_repo, cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype)
elif dst == 'v3':
vae_repo = 'stabilityai/stable-diffusion-3.5-large'
self.vae = AutoencoderKL.from_pretrained(vae_repo, subfolder='vae', cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype)
elif dst == 'fx':
vae_repo = 'black-forest-labs/FLUX.1-dev'
self.vae = AutoencoderKL.from_pretrained(vae_repo, subfolder='vae', cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype)
t0 = time.time()
if self.model is None or self.vae is None:
return None
with torch.no_grad():
latent = latents.clone().cpu().float() # force fp32, always run on CPU
output = self.model(latent)
output = output.to(device=latents.device, dtype=latents.dtype)
t1 = time.time()
shared.log.debug(f'Interposer: src={src}/{list(latents.shape)} dst={dst}/{list(output.shape)} model="{os.path.basename(model_fn)}" vae="{vae_repo}" time={t1-t0:.2f}')
# shared.log.debug(f'Interposer: src={latents.aminmax()} dst={output.aminmax()}')
return output
+2 -2
View File
@@ -81,8 +81,8 @@ class DeepDanbooru:
tag_outformat = tag_outformat.replace('_', ' ')
if shared.opts.deepbooru_escape:
tag_outformat = re.sub(re_special, r'\\\1', tag_outformat)
if shared.opts.deepbooru_clip_score and not force_disable_ranks:
tag_outformat = f"({tag_outformat}:{probability:.3f})"
if shared.opts.interrogate_score and not force_disable_ranks:
tag_outformat = f"({tag_outformat}:{probability:.2f})"
res.append(tag_outformat)
if len(res) > shared.opts.deepbooru_max_tags:
res = res[:shared.opts.deepbooru_max_tags]
+92
View File
@@ -0,0 +1,92 @@
# source: <https://huggingface.co/deepseek-ai/deepseek-vl2-tiny>
# implementation: <https://github.com/deepseek-ai/DeepSeek-VL2/tree/main/deepseek_vl2/serve>
"""
- run `git clone https://github.com/deepseek-ai/DeepSeek-VL2 repositories/deepseek-vl2 --depth 1`
- remove hardcoded `python==3.9` requirement due to obsolete attrdict package dependency
- patch transformers due to internal changes as deepseek requires obsolete `transformers==4.38.2`
- deepseek requires `xformers`
- broken flash_attention
"""
import os
import sys
import importlib
from transformers import AutoModelForCausalLM
from modules import shared, devices, paths
# model_path = "deepseek-ai/deepseek-vl2-small"
vl_gpt = None
vl_chat_processor = None
class fake_attrdict():
class AttrDict(dict): # dot notation access to dictionary attributes
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
# def fake_is_flash_attn_2_available():
# return False
def predict(question, image, repo):
global vl_gpt, vl_chat_processor # pylint: disable=global-statement
if not shared.cmd_opts.experimental:
shared.log.error(f'Interrogate: type=vlm model="DeepSeek VL2" repo="{repo}" is experimental-only')
return ''
folder = os.path.join(paths.script_path, 'repositories', 'deepseek-vl2')
if not os.path.exists(folder):
shared.log.error(f'Interrogate: type=vlm model="DeepSeek VL2" repo="{repo}" deepseek-vl2 repo not found')
return ''
if vl_gpt is None:
sys.modules['attrdict'] = fake_attrdict
from transformers.models.llama import modeling_llama
modeling_llama.LlamaFlashAttention2 = modeling_llama.LlamaAttention
_deekseek_vl = importlib.import_module('repositories.deepseek-vl2.deepseek_vl2')
deekseek_vl_models = importlib.import_module('repositories.deepseek-vl2.deepseek_vl2.models')
vl_chat_processor = deekseek_vl_models.DeepseekVLV2Processor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
vl_gpt = AutoModelForCausalLM.from_pretrained(
repo,
trust_remote_code=True,
cache_dir=shared.opts.hfcache_dir,
)
vl_gpt = vl_gpt.to(device=devices.device, dtype=devices.dtype).eval()
if len(question) < 2:
question = "Describe the image."
question = question.replace('<', '').replace('>', '')
conversation = [
{
"role": "<|User|>",
"content": f"<image>\n<|ref|>{question}<|/ref|>.",
# "images": [image],
},
{"role": "<|Assistant|>", "content": ""},
]
prepare_inputs = vl_chat_processor(
conversations=conversation,
images=[image],
force_batchify=True,
system_prompt=""
).to(device=devices.device, dtype=devices.dtype)
inputs_embeds = vl_gpt.prepare_inputs_embeds(**prepare_inputs)
inputs_embeds = inputs_embeds.to(device=devices.device, dtype=devices.dtype)
vl_gpt = vl_gpt.to(devices.device)
with devices.inference_context():
outputs = vl_gpt.language.generate(
inputs_embeds=inputs_embeds,
attention_mask=prepare_inputs.attention_mask,
pad_token_id=vl_chat_processor.tokenizer.eos_token_id,
bos_token_id=vl_chat_processor.tokenizer.bos_token_id,
eos_token_id=vl_chat_processor.tokenizer.eos_token_id,
max_new_tokens=shared.opts.interrogate_vlm_max_length,
do_sample=False,
use_cache=True
)
vl_gpt = vl_gpt.to(devices.cpu)
answer = vl_chat_processor.tokenizer.decode(outputs[0].cpu().tolist(), skip_special_tokens=True)
print('inputs', prepare_inputs['sft_format'][0])
print('answer', answer)
return answer
+1 -1
View File
@@ -28,7 +28,7 @@ def interrogate(image):
elif shared.opts.interrogate_default_type == 'VLM':
shared.log.info(f'Interrogate: type={shared.opts.interrogate_default_type} vlm="{shared.opts.interrogate_vlm_model}" prompt="{shared.opts.interrogate_vlm_prompt}"')
from modules.interrogate import vqa
prompt = vqa.interrogate(image=image, model_name=shared.opts.interrogate_vlm_model, question=shared.opts.interrogate_vlm_prompt)
prompt = vqa.interrogate(image=image, model_name=shared.opts.interrogate_vlm_model, question=shared.opts.interrogate_vlm_prompt, prompt=None)
shared.log.debug(f'Interrogate: time={time.time()-t0:.2f} answer="{prompt}"')
return prompt
else:
+99
View File
@@ -0,0 +1,99 @@
# based on <https://huggingface.co/fancyfeast/llama-joycaption-alpha-two-hf-llava>
from dataclasses import dataclass
import torch
from transformers import AutoProcessor, LlavaForConditionalGeneration
from modules import shared, devices
"""
Example prompts
Short description: Write a short description of the image.
Detailed descriptive: Please provide a detailed description of the image.
Descriptive: Write a descriptive caption for this image in a formal tone.
Descriptive (Informal): Write a descriptive caption for this image in a casual tone.
Training Prompt: Write a stable diffusion prompt for this image.
MidJourney: Write a MidJourney prompt for this image.
Booru tag list: Write a list of Booru tags for this image.
Booru-like tag list: Write a list of Booru-like tags for this image.
Art Critic: Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc.
Product Listing: Write a caption for this image as though it were a product listing.
Social Media Post: Write a caption for this image as if it were being used for a social media post.
Extra Options:
- If there is a person/character in the image you must refer to them as {name}.
- Do NOT include information about people/characters that cannot be changed (like ethnicity, gender, etc), but do still include changeable attributes (like hair style).
- Include information about lighting.
- Include information about camera angle.
- Include information about whether there is a watermark or not.
- Include information about whether there are JPEG artifacts or not.
- If it is a photo you MUST include information about what camera was likely used and details such as aperture, shutter speed, ISO, etc.
- Do NOT include anything sexual; keep it PG.
- Do NOT mention the image's resolution.
- You MUST include information about the subjective aesthetic quality of the image from low to very high.
- Include information on the image's composition style, such as leading lines, rule of thirds, or symmetry.
- Do NOT mention any text that is in the image.
- Specify the depth of field and whether the background is in focus or blurred.
- If applicable, mention the likely use of artificial or natural lighting sources.
- Do NOT use any ambiguous language.
- Include whether the image is sfw, suggestive, or nsfw.
- ONLY describe the most important elements of the image.
"""
@dataclass
class JoyOptions():
repo: str = "fancyfeast/llama-joycaption-alpha-two-hf-llava"
temp: float = 0.5
top_k: float = 10
top_p: float = 0.9
max_new_tokens: int = 512
sample: bool = True
def __str__(self):
return f'repo="{self.repo}" temp={self.temp} top_k={self.top_k} top_p={self.top_p} sample={self.sample} tokens={self.max_new_tokens}'
processor: AutoProcessor = None
llava_model: LlavaForConditionalGeneration = None
opts = JoyOptions()
@torch.no_grad()
def predict(question: str, image):
global llava_model, processor # pylint: disable=global-statement
opts.max_new_tokens = shared.opts.interrogate_vlm_max_length
if llava_model is None:
shared.log.info(f'Interrogate: type=vlm model="JoyCaption" {str(opts)}')
processor = AutoProcessor.from_pretrained(opts.repo)
llava_model = LlavaForConditionalGeneration.from_pretrained(opts.repo, torch_dtype=devices.dtype, device_map="auto", cache_dir=shared.opts.hfcache_dir)
llava_model.eval()
if len(question) < 2:
question = "Describe the image."
question = question.replace('<', '').replace('>', '')
convo = [
{ "role": "system", "content": "You are a helpful image captioner." },
{ "role": "user", "content": question },
]
convo_string = processor.apply_chat_template(convo, tokenize=False, add_generation_prompt=True)
inputs = processor(text=[convo_string], images=[image], return_tensors="pt").to(devices.device) # Process the inputs
inputs['pixel_values'] = inputs['pixel_values'].to(devices.dtype)
llava_model = llava_model.to(devices.device)
with devices.inference_context():
generate_ids = llava_model.generate( # Generate the captions
**inputs,
# input_ids=inputs['input_ids'],
# pixel_values=inputs['pixel_values'],
# attention_mask=inputs['attention_mask'],
max_new_tokens=opts.max_new_tokens,
suppress_tokens=None,
use_cache=True,
do_sample=opts.sample,
temperature=opts.temp,
top_k=opts.top_k,
top_p=opts.top_p,
)[0]
generate_ids = generate_ids[inputs['input_ids'].shape[1]:] # Trim off the prompt
caption = processor.tokenizer.decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False) # Decode the caption
llava_model = llava_model.to(devices.cpu)
caption = caption.replace('\n\n', '\n').strip()
return caption
File diff suppressed because it is too large Load Diff
+53 -72
View File
@@ -6,21 +6,13 @@ import threading
import re
import torch
import torch.hub # pylint: disable=ungrouped-imports
import gradio as gr
from PIL import Image
from torchvision import transforms
from torchvision.transforms.functional import InterpolationMode
from modules import devices, paths, shared, lowvram, errors, sd_models
config = {
"caption_max_length": 74,
"chunk_size": 1024,
"flavor_intermediate_count": 1024,
"min_flavors": 2,
"max_flavors": 8,
"clip_offload": True,
"caption_offload": True,
}
caption_models = {
'blip-base': 'Salesforce/blip-image-captioning-base',
'blip-large': 'Salesforce/blip-image-captioning-large',
@@ -213,8 +205,8 @@ class InterrogateModels:
for _name, topn, items in self.categories():
matches = self.rank(image_features, items, top_count=topn)
for match, score in matches:
if shared.opts.interrogate_clip_score:
res += f", ({match}:{score/100:.3f})"
if shared.opts.interrogate_score:
res += f", ({match}:{score/100:.2f})"
else:
res += f", {match}"
except Exception as e:
@@ -227,13 +219,17 @@ class InterrogateModels:
# --------- interrrogate ui
class BatchWriter:
def __init__(self, folder):
def __init__(self, folder, mode='w'):
self.folder = folder
self.csv, self.file = None, None
self.csv = None
self.file = None
self.mode = mode
def add(self, file, prompt):
txt_file = os.path.splitext(file)[0] + ".txt"
with open(os.path.join(self.folder, txt_file), 'w', encoding='utf-8') as f:
if self.mode == 'a':
prompt = '\n' + prompt
with open(os.path.join(self.folder, txt_file), self.mode, encoding='utf-8') as f:
f.write(prompt)
def close(self):
@@ -241,21 +237,13 @@ class BatchWriter:
self.file.close()
def update_interrogate_params(caption_max_length:int=None, chunk_size:int=None, min_flavors:int=None, max_flavors:int=None, flavor_intermediate_count:int=None):
config["caption_max_length"] = int(caption_max_length or shared.opts.interrogate_clip_max_length)
config["clip_offload"] = shared.opts.interrogate_offload
config["caption_offload"] = shared.opts.interrogate_offload
config["min_flavors"] = int(min_flavors or shared.opts.interrogate_clip_min_flavors)
config["max_flavors"] = int(max_flavors or shared.opts.interrogate_clip_max_flavors)
if chunk_size is not None:
config["chunk_size"] = int(chunk_size)
if flavor_intermediate_count is not None:
config["flavor_intermediate_count"] = int(flavor_intermediate_count)
def update_interrogate_params():
if ci is not None:
ci.config.caption_max_length = config["caption_max_length"]
ci.config.chunk_size = config["chunk_size"]
ci.config.flavor_intermediate_count = config["flavor_intermediate_count"]
shared.log.debug(f'Interrogate: type={shared.opts.interrogate_default_type} config={config}')
ci.caption_max_length=shared.opts.interrogate_clip_max_length
ci.chunk_size=shared.opts.interrogate_clip_chunk_size
ci.flavor_intermediate_count=shared.opts.interrogate_clip_flavor_count
ci.clip_offload=shared.opts.interrogate_offload
ci.caption_offload=shared.opts.interrogate_offload
def get_clip_models():
@@ -285,11 +273,11 @@ def load_interrogator(clip_model, blip_model):
clip_model_name=clip_model,
caption_model_name=blip_model,
quiet=True,
caption_max_length=config['caption_max_length'],
chunk_size=config['chunk_size'],
flavor_intermediate_count=config['flavor_intermediate_count'],
clip_offload=config['clip_offload'],
caption_offload=config['caption_offload'],
caption_max_length=shared.opts.interrogate_clip_max_length,
chunk_size=shared.opts.interrogate_clip_chunk_size,
flavor_intermediate_count=shared.opts.interrogate_clip_flavor_count,
clip_offload=shared.opts.interrogate_offload,
caption_offload=shared.opts.interrogate_offload,
)
ci = clip_interrogator.Interrogator(interrogator_config)
elif clip_model != ci.config.clip_model_name or blip_model != ci.config.caption_model_name:
@@ -319,15 +307,15 @@ def interrogate(image, mode, caption=None):
return ''
image = image.convert("RGB")
if mode == 'best':
prompt = ci.interrogate(image, caption=caption, min_flavors=config["min_flavors"], max_flavors=config["max_flavors"])
prompt = ci.interrogate(image, caption=caption, min_flavors=shared.opts.interrogate_clip_min_flavors, max_flavors=shared.opts.interrogate_clip_max_flavors, )
elif mode == 'caption':
prompt = ci.generate_caption(image) if caption is None else caption
elif mode == 'classic':
prompt = ci.interrogate_classic(image, caption=caption, max_flavors=config["max_flavors"])
prompt = ci.interrogate_classic(image, caption=caption, max_flavors=shared.opts.interrogate_clip_max_flavors)
elif mode == 'fast':
prompt = ci.interrogate_fast(image, caption=caption, max_flavors=config["max_flavors"])
prompt = ci.interrogate_fast(image, caption=caption, max_flavors=shared.opts.interrogate_clip_max_flavors)
elif mode == 'negative':
prompt = ci.interrogate_negative(image, max_flavors=config["max_flavors"])
prompt = ci.interrogate_negative(image, max_flavors=shared.opts.interrogate_clip_max_flavors)
else:
raise RuntimeError(f"Unknown mode {mode}")
return prompt
@@ -339,7 +327,7 @@ def interrogate_image(image, clip_model, blip_model, mode):
if not shared.native and (shared.cmd_opts.lowvram or shared.cmd_opts.medvram):
lowvram.send_everything_to_cpu()
devices.torch_gc()
if shared.native:
if shared.native and shared.sd_loaded:
sd_models.apply_balanced_offload(shared.sd_model)
load_interrogator(clip_model, blip_model)
image = image.convert('RGB')
@@ -353,58 +341,45 @@ def interrogate_image(image, clip_model, blip_model, mode):
return prompt
def interrogate_batch(batch_files, batch_folder, batch_str, clip_model, blip_model, mode, write):
def interrogate_batch(batch_files, batch_folder, batch_str, clip_model, blip_model, mode, write, append, recursive):
files = []
if batch_files is not None:
files += [f.name for f in batch_files]
if batch_folder is not None:
files += [f.name for f in batch_folder]
if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str):
files += [os.path.join(batch_str, f) for f in os.listdir(batch_str) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]
from modules.files_cache import list_files
files += list(list_files(batch_str, ext_filter=['.png', '.jpg', '.jpeg', '.webp'], recursive=recursive))
if len(files) == 0:
shared.log.error('Interrogate batch no images')
shared.log.warning('Interrogate batch: type=clip no images')
return ''
shared.state.begin('Batch interrogate')
shared.state.begin('Interrogate batch')
prompts = []
try:
if not shared.native and (shared.cmd_opts.lowvram or shared.cmd_opts.medvram):
lowvram.send_everything_to_cpu()
devices.torch_gc()
load_interrogator(clip_model, blip_model)
shared.log.info(f'Interrogate batch: images={len(files)} mode={mode} config={ci.config}')
captions = []
# first pass: generate captions
load_interrogator(clip_model, blip_model)
if write:
file_mode = 'w' if not append else 'a'
writer = BatchWriter(os.path.dirname(files[0]), mode=file_mode)
import rich.progress as rp
pbar = rp.Progress(rp.TextColumn('[cyan]Caption:'), rp.BarColumn(), rp.MofNCompleteColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=shared.console)
with pbar:
task = pbar.add_task(total=len(files), description='starting...')
for file in files:
caption = ""
pbar.update(task, advance=1, description=file)
try:
if shared.state.interrupted:
break
image = Image.open(file).convert('RGB')
caption = ci.generate_caption(image)
except Exception as e:
shared.log.error(f'Interrogate caption: {e}')
finally:
captions.append(caption)
# second pass: interrogate
if write:
writer = BatchWriter(os.path.dirname(files[0]))
for idx, file in enumerate(files):
try:
if shared.state.interrupted:
break
image = Image.open(file).convert('RGB')
prompt = interrogate(image, mode, caption=captions[idx])
prompt = interrogate(image, mode)
prompts.append(prompt)
if write:
writer.add(file, prompt)
except OSError as e:
shared.log.error(f'Interrogate batch: {e}')
if write:
writer.close()
ci.config.quiet = False
unload_clip_model()
except Exception as e:
shared.log.error(f'Interrogate batch: {e}')
if write:
writer.close()
ci.config.quiet = False
unload_clip_model()
shared.state.end()
return '\n\n'.join(prompts)
@@ -423,7 +398,13 @@ def analyze_image(image, clip_model, blip_model):
movement_ranks = dict(zip(top_movements, ci.similarities(image_features, top_movements)))
trending_ranks = dict(zip(top_trendings, ci.similarities(image_features, top_trendings)))
flavor_ranks = dict(zip(top_flavors, ci.similarities(image_features, top_flavors)))
return medium_ranks, artist_ranks, movement_ranks, trending_ranks, flavor_ranks
return [
gr.update(value=medium_ranks, visible=True),
gr.update(value=artist_ranks, visible=True),
gr.update(value=movement_ranks, visible=True),
gr.update(value=trending_ranks, visible=True),
gr.update(value=flavor_ranks, visible=True),
]
interrogator = InterrogateModels()
+225 -33
View File
@@ -1,4 +1,5 @@
import io
import os
import time
import json
import base64
@@ -6,12 +7,7 @@ import torch
import transformers
import transformers.dynamic_module_utils
from PIL import Image
from modules import shared, devices, errors
# TODO vlm: add additional models
# https://huggingface.co/nvidia/Eagle2-1B not compatible with latest transformers
# https://huggingface.co/deepseek-ai/deepseek-vl2-tiny requires custom code
from modules import shared, devices, errors, sd_models
processor = None
model = None
@@ -36,7 +32,17 @@ vlm_models = {
"Microsoft GIT VQA Base": "microsoft/git-base-vqav2", # 0.7GB
"Microsoft GIT VQA Large": "microsoft/git-large-vqav2", # 1.6GB
"ToriiGate 0.4 2B": "Minthy/ToriiGate-v0.4-2B",
"ToriiGate 0.4 7B": "Minthy/ToriiGate-v0.4-7B",
"ViLT Base": "dandelin/vilt-b32-finetuned-vqa", # 0.5GB
"Google PaliGemma 2 3B": "google/paligemma2-3b-pt-224",
"JoyCaption": "fancyfeast/llama-joycaption-alpha-two-hf-llava", # 0.7GB
"JoyTag": "fancyfeast/joytag", # 17.4GB
"AIDC Ovis2 1B": "AIDC-AI/Ovis2-1B",
"AIDC Ovis2 2B": "AIDC-AI/Ovis2-2B",
"AIDC Ovis2 4B": "AIDC-AI/Ovis2-4B",
# "OpenGVLab InternVL 2.5 1B": "OpenGVLab/InternVL2_5-1B"
# "DeepSeek VL2 Tiny": "deepseek-ai/deepseek-vl2-tiny", # broken
# "nVidia Eagle 2 1B": "nvidia/Eagle2-1B", # not compatible with latest transformers
}
vlm_prompts = [
'<CAPTION>',
@@ -72,25 +78,46 @@ def clean(response, question):
response = json.dumps(response)
if isinstance(response, list):
response = response[0]
question = question.replace('<', '').replace('>', '')
question = question.replace('<', '').replace('>', '').replace('_', ' ')
if question in response:
response = response.split(question, 1)[1]
response = response.replace('\n', '').replace('\r', '').replace('\t', '').strip()
if response.startswith('"'):
response = response[1:]
if response.endswith('"'):
response = response[:-1]
response = response.replace('Assistant:', '').strip()
return response
def get_kwargs():
kwargs = {
'max_new_tokens': shared.opts.interrogate_vlm_max_length,
'do_sample': shared.opts.interrogate_vlm_do_sample,
}
if shared.opts.interrogate_vlm_num_beams > 0:
kwargs['num_beams'] = shared.opts.interrogate_vlm_num_beams
if shared.opts.interrogate_vlm_temperature > 0:
kwargs['temperature'] = shared.opts.interrogate_vlm_temperature
if shared.opts.interrogate_vlm_top_k > 0:
kwargs['top_k'] = shared.opts.interrogate_vlm_top_k
if shared.opts.interrogate_vlm_top_p > 0:
kwargs['top_p'] = shared.opts.interrogate_vlm_top_p
return kwargs
def qwen(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.Qwen2VLForConditionalGeneration.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.Qwen2VLForConditionalGeneration.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir
)
processor = transformers.AutoProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device, devices.dtype)
if len(question) < 2:
question = "Describe the image."
question = question.replace('<', '').replace('>', '')
model = model.to(devices.device, devices.dtype)
question = question.replace('<', '').replace('>', '').replace('_', ' ')
conversation = [
{
"role": "system",
@@ -112,7 +139,7 @@ def qwen(question: str, image: Image.Image, repo: str = None):
inputs = inputs.to(devices.device, devices.dtype)
output_ids = model.generate(
**inputs,
max_new_tokens=shared.opts.interrogate_vlm_max_length,
**get_kwargs(),
)
generated_ids = [
output_ids[len(input_ids) :]
@@ -122,6 +149,76 @@ def qwen(question: str, image: Image.Image, repo: str = None):
return response
def paligemma(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
processor = transformers.PaliGemmaProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.PaliGemmaForConditionalGeneration.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir,
torch_dtype=devices.dtype,
)
loaded = repo
model = model.to(devices.device, devices.dtype)
question = question.replace('<', '').replace('>', '').replace('_', ' ')
model_inputs = processor(text=question, images=image, return_tensors="pt").to(devices.device, devices.dtype)
input_len = model_inputs["input_ids"].shape[-1]
with devices.inference_context():
generation = model.generate(
**model_inputs,
**get_kwargs(),
)
generation = generation[0][input_len:]
response = processor.decode(generation, skip_special_tokens=True)
return response
def ovis(question: str, image: Image.Image, repo: str = None):
try:
import flash_attn # pylint: disable=unused-import
except Exception:
shared.log.error(f'Interrogate: vlm="{repo}" flash-attn is not available')
return ''
global model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.AutoModelForCausalLM.from_pretrained(
repo,
torch_dtype=devices.dtype,
multimodal_max_length=32768,
trust_remote_code=True,
cache_dir=shared.opts.hfcache_dir,
)
loaded = repo
model = model.to(devices.device, devices.dtype)
text_tokenizer = model.get_text_tokenizer()
visual_tokenizer = model.get_visual_tokenizer()
max_partition = 9
question = question.replace('<', '').replace('>', '').replace('_', ' ')
question = f'<image>\n{question}'
_prompt, input_ids, pixel_values = model.preprocess_inputs(question, [image], max_partition=max_partition)
attention_mask = torch.ne(input_ids, text_tokenizer.pad_token_id)
input_ids = input_ids.unsqueeze(0).to(device=model.device)
attention_mask = attention_mask.unsqueeze(0).to(device=model.device)
if pixel_values is not None:
pixel_values = pixel_values.to(dtype=visual_tokenizer.dtype, device=visual_tokenizer.device)
pixel_values = [pixel_values]
with devices.inference_context():
output_ids = model.generate(
input_ids,
pixel_values=pixel_values,
attention_mask=attention_mask,
repetition_penalty=None,
eos_token_id=model.generation_config.eos_token_id,
pad_token_id=text_tokenizer.pad_token_id,
use_cache=True,
**get_kwargs())
response = text_tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(f'Output:\n{response}')
return response
def smol(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
@@ -135,9 +232,7 @@ def smol(question: str, image: Image.Image, repo: str = None):
processor = transformers.AutoProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device, devices.dtype)
if len(question) < 2:
question = "Describe the image."
question = question.replace('<', '').replace('>', '')
question = question.replace('<', '').replace('>', '').replace('_', ' ')
conversation = [
{
"role": "system",
@@ -159,7 +254,7 @@ def smol(question: str, image: Image.Image, repo: str = None):
inputs = inputs.to(devices.device, devices.dtype)
output_ids = model.generate(
**inputs,
max_new_tokens=shared.opts.interrogate_vlm_max_length,
**get_kwargs(),
)
response = processor.batch_decode(output_ids,skip_special_tokens=True)
return response
@@ -169,7 +264,10 @@ def git(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.GitForCausalLM.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.GitForCausalLM.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir,
)
processor = transformers.GitProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device, devices.dtype)
@@ -191,7 +289,10 @@ def blip(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.BlipForQuestionAnswering.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.BlipForQuestionAnswering.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir,
)
processor = transformers.BlipProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device, devices.dtype)
@@ -207,7 +308,10 @@ def vilt(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.ViltForQuestionAnswering.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.ViltForQuestionAnswering.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir,
)
processor = transformers.ViltProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device)
@@ -225,7 +329,10 @@ def pix(question: str, image: Image.Image, repo: str = None):
global processor, model, loaded # pylint: disable=global-statement
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}"')
model = transformers.Pix2StructForConditionalGeneration.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
model = transformers.Pix2StructForConditionalGeneration.from_pretrained(
repo,
cache_dir=shared.opts.hfcache_dir,
)
processor = transformers.Pix2StructProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir)
loaded = repo
model.to(devices.device)
@@ -253,9 +360,7 @@ def moondream(question: str, image: Image.Image, repo: str = None):
loaded = repo
model.eval()
model.to(devices.device, devices.dtype)
if len(question) < 2:
question = "Describe the image."
question = question.replace('<', '').replace('>', '')
question = question.replace('<', '').replace('>', '').replace('_', ' ')
encoded = model.encode_image(image)
with devices.inference_context():
response = model.answer_question(encoded, question, processor)
@@ -272,11 +377,16 @@ def florence(question: str, image: Image.Image, repo: str = None, revision: str
return R
revision = None
if '@' in repo:
repo, revision = model.split('@')
repo, revision = repo.split('@')
if model is None or loaded != repo:
shared.log.debug(f'Interrogate load: vlm="{repo}" path="{shared.opts.hfcache_dir}"')
transformers.dynamic_module_utils.get_imports = get_imports
model = transformers.AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True, revision=revision, cache_dir=shared.opts.hfcache_dir)
model = transformers.AutoModelForCausalLM.from_pretrained(
repo,
trust_remote_code=True,
revision=revision,
cache_dir=shared.opts.hfcache_dir,
)
processor = transformers.AutoProcessor.from_pretrained(repo, trust_remote_code=True, revision=revision, cache_dir=shared.opts.hfcache_dir)
transformers.dynamic_module_utils.get_imports = _get_imports
loaded = repo
@@ -286,7 +396,6 @@ def florence(question: str, image: Image.Image, repo: str = None, revision: str
task = question.split('>', 1)[0] + '>'
else:
task = '<MORE_DETAILED_CAPTION>'
# question = task + question
inputs = processor(text=task, images=image, return_tensors="pt")
input_ids = inputs['input_ids'].to(devices.device)
pixel_values = inputs['pixel_values'].to(devices.device, devices.dtype)
@@ -294,16 +403,16 @@ def florence(question: str, image: Image.Image, repo: str = None, revision: str
generated_ids = model.generate(
input_ids=input_ids,
pixel_values=pixel_values,
max_new_tokens=shared.opts.interrogate_vlm_max_length,
num_beams=shared.opts.interrogate_vlm_num_beams,
do_sample=False
**get_kwargs()
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
response = processor.post_process_generation(generated_text, task="task", image_size=(image.width, image.height))
return response
def interrogate(question, image, model_name):
def interrogate(question, prompt, image, model_name, quiet:bool=False):
if not quiet:
shared.state.begin('Interrogate')
t0 = time.time()
if isinstance(image, list):
image = image[0] if len(image) > 0 else None
@@ -312,9 +421,17 @@ def interrogate(question, image, model_name):
if image is None:
return ''
if image.width > 768 or image.height > 768:
image.thumbnail((768, 768), Image.Resampling.HAMMING)
image.thumbnail((768, 768), Image.Resampling.LANCZOS)
if image.mode != 'RGB':
image = image.convert('RGB')
if prompt is not None and len(prompt) > 0:
question = prompt
if len(question) < 2:
question = "Describe the image."
if shared.native and shared.sd_loaded:
sd_models.apply_balanced_offload(shared.sd_model)
from modules import modelloader
modelloader.hf_login()
try:
if model_name is None:
shared.log.error(f'Interrogate: type=vlm model="{model_name}" no model selected')
@@ -342,6 +459,19 @@ def interrogate(question, image, model_name):
answer = qwen(question, image, vqa_model)
elif 'smol' in vqa_model.lower():
answer = smol(question, image, vqa_model)
elif 'joytag' in vqa_model.lower():
from modules.interrogate import joytag
answer = joytag.predict(image)
elif 'joycaption' in vqa_model.lower():
from modules.interrogate import joycaption
answer = joycaption.predict(question, image)
elif 'deepseek' in vqa_model.lower():
from modules.interrogate import deepseek
answer = deepseek.predict(question, image, vqa_model)
elif 'paligemma' in vqa_model.lower():
answer = paligemma(question, image, vqa_model)
elif 'ovis' in vqa_model.lower():
answer = ovis(question, image, vqa_model)
else:
answer = 'unknown model'
except Exception as e:
@@ -352,5 +482,67 @@ def interrogate(question, image, model_name):
devices.torch_gc()
answer = clean(answer, question)
t1 = time.time()
shared.log.debug(f'Interrogate: type=vlm model="{model_name}" repo="{vqa_model}" time={t1-t0:.2f}')
if not quiet:
shared.log.debug(f'Interrogate: type=vlm model="{model_name}" repo="{vqa_model}" args={get_kwargs()} time={t1-t0:.2f}')
shared.state.end()
return answer
def batch(model_name, batch_files, batch_folder, batch_str, question, prompt, write, append, recursive):
class BatchWriter:
def __init__(self, folder, mode='w'):
self.folder = folder
self.csv = None
self.file = None
self.mode = mode
def add(self, file, prompt):
txt_file = os.path.splitext(file)[0] + ".txt"
if self.mode == 'a':
prompt = '\n' + prompt
with open(os.path.join(self.folder, txt_file), self.mode, encoding='utf-8') as f:
f.write(prompt)
def close(self):
if self.file is not None:
self.file.close()
files = []
if batch_files is not None:
files += [f.name for f in batch_files]
if batch_folder is not None:
files += [f.name for f in batch_folder]
if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str):
from modules.files_cache import list_files
files += list(list_files(batch_str, ext_filter=['.png', '.jpg', '.jpeg', '.webp'], recursive=recursive))
if len(files) == 0:
shared.log.warning('Interrogate batch: type=vlm no images')
return ''
shared.state.begin('Interrogate batch')
prompts = []
if write:
mode = 'w' if not append else 'a'
writer = BatchWriter(os.path.dirname(files[0]), mode=mode)
orig_offload = shared.opts.interrogate_offload
shared.opts.interrogate_offload = False
import rich.progress as rp
pbar = rp.Progress(rp.TextColumn('[cyan]Caption:'), rp.BarColumn(), rp.MofNCompleteColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=shared.console)
with pbar:
task = pbar.add_task(total=len(files), description='starting...')
for file in files:
pbar.update(task, advance=1, description=file)
try:
if shared.state.interrupted:
break
image = Image.open(file)
prompt = interrogate(question, prompt, image, model_name, quiet=True)
prompts.append(prompt)
if write:
writer.add(file, prompt)
except Exception as e:
shared.log.error(f'Interrogate batch: {e}')
if write:
writer.close()
shared.opts.interrogate_offload = orig_offload
shared.state.end()
return '\n\n'.join(prompts)
+3 -3
View File
@@ -6,7 +6,7 @@ import numpy as np
from torch.hub import download_url_to_file, get_dir
from PIL import Image
from modules import devices
from modules.shared import log
from installer import log
LAMA_MODEL_URL = "https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt"
@@ -38,7 +38,7 @@ def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor=
mode="symmetric",
)
def scale_image(img, factor, interpolation=cv2.INTER_AREA):
def scale_image(img, factor, interpolation=cv2.INTER_LANCZOS4):
if img.shape[0] == 1:
img = img[0]
else:
@@ -54,7 +54,7 @@ def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor=
out_mask = get_image(mask)
if scale_factor is not None:
out_image = scale_image(out_image, scale_factor)
out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_NEAREST)
out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_LANCZOS4)
if pad_out_to_modulo is not None and pad_out_to_modulo > 1:
out_image = pad_img_to_modulo(out_image, pad_out_to_modulo)
out_mask = pad_img_to_modulo(out_mask, pad_out_to_modulo)
+1 -1
View File
@@ -272,7 +272,7 @@ class TransparentVAEDecoder(AutoencoderKL):
B, H, W, C = fg.shape
cb = checkerboard(shape=(H // 64, W // 64))
cb = cv2.resize(cb, (W, H), interpolation=cv2.INTER_NEAREST)
cb = cv2.resize(cb, (W, H), interpolation=cv2.INTER_LANCZOS4)
cb = (0.5 + (cb - 0.5) * 0.1)[None, ..., None]
cb = torch.from_numpy(cb).to(fg)
+1 -1
View File
@@ -140,4 +140,4 @@ except ImportError:
pass # shrug...
errors.log.info(f'Torch: torch=={torch.__version__} torchvision=={torchvision.__version__}')
errors.log.info(f'Packages: diffusers=={diffusers.__version__} transformers=={transformers.__version__} accelerate=={accelerate.__version__} gradio=={gradio.__version__}')
errors.log.info(f'Packages: diffusers=={diffusers.__version__} transformers=={transformers.__version__} accelerate=={accelerate.__version__} gradio=={gradio.__version__} pydantic=={pydantic.__version__}')
+35 -26
View File
@@ -238,7 +238,7 @@ def run_segment(input_image: gr.Image, input_mask: np.ndarray):
overlap = 0
if input_mask_size > 0:
if mask.shape != input_mask.shape:
mask = cv2.resize(mask, (input_mask.shape[1], input_mask.shape[0]), interpolation=cv2.INTER_CUBIC)
mask = cv2.resize(mask, (input_mask.shape[1], input_mask.shape[0]), interpolation=cv2.INTER_LANCZOS4)
overlap = cv2.bitwise_and(mask, input_mask)
overlap = np.count_nonzero(overlap)
if overlap == 0:
@@ -278,7 +278,7 @@ def run_rembg(input_image: Image, input_mask: np.ndarray):
binary_input = cv2.threshold(input_mask, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
binary_output = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
if binary_input.shape != binary_output.shape:
binary_output = cv2.resize(binary_output, binary_input.shape[:2], interpolation=cv2.INTER_LINEAR)
binary_output = cv2.resize(binary_output, binary_input.shape[:2], interpolation=cv2.INTER_LANCZOS4)
binary_overlap = cv2.bitwise_and(binary_input, binary_output)
input_size = np.count_nonzero(binary_input)
overlap_size = np.count_nonzero(binary_overlap)
@@ -335,6 +335,8 @@ def get_mask(input_image: gr.Image, input_mask: gr.Image):
def outpaint(input_image: Image.Image, outpaint_type: str = 'Edge'):
fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access
debug(f'Run outpaint: fn={fn}') # pylint: disable=protected-access
image = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
h0, w0 = image.shape[:2]
empty = (image == 0).all(axis=2)
@@ -342,35 +344,43 @@ def outpaint(input_image: Image.Image, outpaint_type: str = 'Edge'):
x1, x2 = min(x0), max(x0)
y1, y2 = min(y0), max(y0)
cropped = image[y1:y2, x1:x2]
h1, w1 = cropped.shape[:2]
mask = None
if opts.mask_only:
mask = cv2.copyMakeBorder(cropped, y1, h0-y2, x1, w0-x2, cv2.BORDER_CONSTANT, value=(0, 0, 0))
mask = cv2.resize(mask, (w0, h0))
mask = cv2.cvtColor(np.array(mask), cv2.COLOR_BGR2GRAY)
mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)[1]
sigmaX, sigmaY = int((h0-h1)/3), int((w0-w1)/3)
sigmaX, sigmaY = max(1, sigmaX), max(1, sigmaY)
kernel = np.ones((5, 5), np.uint8)
mask = cv2.erode(mask, kernel, iterations=max(sigmaX, sigmaY) // 3) # increase overlap area
mask = cv2.GaussianBlur(mask, (0, 0), sigmaX=sigmaX, sigmaY=sigmaY) # blur mask
mask = Image.fromarray(mask)
mask = cv2.copyMakeBorder(cropped, y1, h0-y2, x1, w0-x2, cv2.BORDER_CONSTANT, value=(0, 0, 0))
mask = cv2.resize(mask, (w0, h0))
mask = cv2.cvtColor(np.array(mask), cv2.COLOR_BGR2GRAY)
mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)[1]
"""
size = min(input_image.width, input_image.height)
if opts.mask_erode > 0:
try:
kernel = np.ones((int(opts.mask_erode * size / 4) + 1, int(opts.mask_erode * size / 4) + 1), np.uint8)
mask = cv2.erode(mask, kernel, iterations=opts.kernel_iterations) # remove noise
debug(f'Mask erode={opts.mask_erode:.3f} kernel={kernel.shape} mask={mask.shape}')
except Exception as e:
shared.log.error(f'Mask erode: {e}')
if opts.mask_dilate > 0:
try:
kernel = np.ones((int(opts.mask_dilate * size / 4) + 1, int(opts.mask_dilate * size / 4) + 1), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=opts.kernel_iterations) # expand area
debug(f'Mask dilate={opts.mask_dilate:.3f} kernel={kernel.shape} mask={mask.shape}')
except Exception as e:
shared.log.error(f'Mask dilate: {e}')
if opts.mask_blur > 0:
try:
sigmax, sigmay = 1 + int(opts.mask_blur * size / 4), 1 + int(opts.mask_blur * size / 4)
mask = cv2.GaussianBlur(mask, (0, 0), sigmaX=sigmax, sigmaY=sigmay) # blur mask
debug(f'Mask blur={opts.mask_blur:.3f} x={sigmax} y={sigmay} mask={mask.shape}')
except Exception as e:
shared.log.error(f'Mask blur: {e}')
"""
if outpaint_type == 'Edge':
bordered = cv2.copyMakeBorder(cropped, y1, h0-y2, x1, w0-x2, cv2.BORDER_REPLICATE)
bordered = cv2.resize(bordered, (w0, h0))
image = bordered
# noise = np.random.normal(1, variation, bordered.shape)
# noised = (noise * bordered).astype(np.uint8)
# h, w = cropped.shape[:2]
# noised[y1:y1 + h, x1:x1 + w] = cropped # overlay original over initialized
# image = noised
# mask = Image.new('L', (w0, h0), 0)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
mask = Image.fromarray(mask)
return image, mask
@@ -409,9 +419,9 @@ def run_mask(input_image: Image.Image, input_mask: Image.Image = None, return_ty
mask = run_rembg(input_image, input_mask)
else:
mask = run_segment(input_image, input_mask)
mask = cv2.resize(mask, (input_image.width, input_image.height), interpolation=cv2.INTER_LINEAR)
mask = cv2.resize(mask, (input_image.width, input_image.height), interpolation=cv2.INTER_LANCZOS4)
debug(f'Mask shape={mask.shape} opts={opts}')
shared.log.trace(f'Mask shape={mask.shape} opts={opts} fn={fn}')
if opts.mask_erode > 0:
try:
kernel = np.ones((int(opts.mask_erode * size / 4) + 1, int(opts.mask_erode * size / 4) + 1), np.uint8)
@@ -436,7 +446,6 @@ def run_mask(input_image: Image.Image, input_mask: Image.Image = None, return_ty
if opts.invert:
mask = np.invert(mask)
return_type = return_type or opts.preview_type
# mask_size = np.count_nonzero(mask)
+1 -1
View File
@@ -7,7 +7,7 @@ import torch
from tensordict import TensorDict
import modules.memstats
import modules.devices as devices
from modules.shared import log, console
from installer import log, console
from modules.sd_models import read_state_dict
from modules.merging import merge_methods
from modules.merging.merge_utils import WeightClass
+1 -1
View File
@@ -4,7 +4,7 @@ from random import shuffle
from typing import NamedTuple
import torch
from scipy.optimize import linear_sum_assignment
from modules.shared import log
from installer import log
SPECIAL_KEYS = [
File diff suppressed because it is too large Load Diff
+20 -17
View File
@@ -144,24 +144,27 @@ def quant_flux_bnb(checkpoint_info, transformer, text_encoder_2):
def load_quants(kwargs, repo_id, cache_dir, allow_quant):
if not allow_quant:
return kwargs
quant_args = {}
quant_args = model_quant.create_bnb_config(quant_args)
if quant_args:
model_quant.load_bnb(f'Load model: type=FLUX quant={quant_args}')
if not quant_args:
quant_args = model_quant.create_ao_config(quant_args)
try:
if not allow_quant:
return kwargs
quant_args = {}
quant_args = model_quant.create_bnb_config(quant_args)
if quant_args:
model_quant.load_torchao(f'Load model: type=FLUX quant={quant_args}')
if not quant_args:
return kwargs
if 'transformer' not in kwargs and ('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization):
kwargs['transformer'] = diffusers.FluxTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args)
shared.log.debug(f'Quantization: module=transformer type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}')
if 'text_encoder_2' not in kwargs and ('Text Encoder' in shared.opts.bnb_quantization or 'Text Encoder' in shared.opts.torchao_quantization):
kwargs['text_encoder_2'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_2", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args)
shared.log.debug(f'Quantization: module=t5 type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}')
model_quant.load_bnb(f'Load model: type=FLUX quant={quant_args}')
if not quant_args:
quant_args = model_quant.create_ao_config(quant_args)
if quant_args:
model_quant.load_torchao(f'Load model: type=FLUX quant={quant_args}')
if not quant_args:
return kwargs
if 'transformer' not in kwargs and ('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization):
kwargs['transformer'] = diffusers.FluxTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args)
shared.log.debug(f'Quantization: module=transformer type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}')
if 'text_encoder_2' not in kwargs and ('Text Encoder' in shared.opts.bnb_quantization or 'Text Encoder' in shared.opts.torchao_quantization):
kwargs['text_encoder_2'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_2", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args)
shared.log.debug(f'Quantization: module=t5 type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}')
except Exception as e:
shared.log.error(f'Quantization: {e}')
return kwargs
+21
View File
@@ -1,3 +1,4 @@
import transformers
import diffusers
@@ -22,3 +23,23 @@ def load_lumina(_checkpoint_info, diffusers_load_config={}):
)
devices.torch_gc()
return pipe
def load_lumina2(checkpoint_info, diffusers_load_config={}):
from modules import shared, devices, sd_models, model_quant
quant_args = {}
quant_args = model_quant.create_bnb_config(quant_args)
if quant_args:
model_quant.load_bnb(f'Load model: type=Lumina quant={quant_args}')
if not quant_args:
quant_args = model_quant.create_ao_config(quant_args)
if quant_args:
model_quant.load_torchao(f'Load model: type=Lumina quant={quant_args}')
kwargs = {}
repo_id = sd_models.path_to_repo(checkpoint_info.name)
if ('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization):
kwargs['transformer'] = diffusers.Lumina2Transformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype, **quant_args)
if ('Text Encoder' in shared.opts.bnb_quantization or 'Text Encoder' in shared.opts.torchao_quantization):
kwargs['text_encoder'] = transformers.AutoModel.from_pretrained(repo_id, subfolder="text_encoder", cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype, **quant_args)
sd_model = diffusers.Lumina2Text2ImgPipeline.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config, **quant_args, **kwargs)
return sd_model
+1 -3
View File
@@ -30,8 +30,6 @@ def get_quant(name):
return 'none'
def create_bnb_config(kwargs = None, allow_bnb: bool = True):
from modules import shared, devices
if len(shared.opts.bnb_quantization) > 0 and allow_bnb:
@@ -77,7 +75,7 @@ def load_torchao(msg='', silent=False):
global ao # pylint: disable=global-statement
if ao is not None:
return ao
install('torchao==0.7.0', quiet=True)
install('torchao==0.8.0', quiet=True)
try:
import torchao
ao = torchao
+2
View File
@@ -31,6 +31,8 @@ def get_model_type(pipe):
model_type = 'f1'
elif "Mochi" in name:
model_type = 'mochi'
elif "Lumina2" in name:
model_type = 'lumina2'
elif "Lumina" in name:
model_type = 'lumina'
elif "OmniGen" in name:
+3 -1
View File
@@ -110,6 +110,8 @@ def download_civit_model_thread(model_name: str, model_url: str, model_path: str
if os.path.isfile(temp_file):
starting_pos = os.path.getsize(temp_file)
headers['Range'] = f'bytes={starting_pos}-'
if token is None:
token = shared.opts.civitai_token
if token is not None and len(token) > 0:
headers['Authorization'] = f'Bearer {token}'
@@ -396,7 +398,7 @@ def load_civitai(model: str, url: str):
return name # already downloaded
else:
shared.log.debug(f'Reference download start: model="{name}"')
download_civit_model_thread(model_name=model, model_url=url, model_path='', model_type='safetensors', token=None)
download_civit_model_thread(model_name=model, model_url=url, model_path='', model_type='safetensors', token=shared.opts.civitai_token)
shared.log.debug(f'Reference download complete: model="{name}"')
sd_models.list_models()
info = sd_models.get_closet_checkpoint_match(name)
+5 -5
View File
@@ -45,12 +45,12 @@ def center_crop_arr(pil_image, image_size):
"""
while min(*pil_image.size) >= 2 * image_size:
pil_image = pil_image.resize(
tuple(x // 2 for x in pil_image.size), resample=Image.BOX
tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS
)
scale = image_size / min(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS
)
arr = np.array(pil_image)
@@ -63,19 +63,19 @@ def center_crop_arr(pil_image, image_size):
def crop_arr(pil_image, max_image_size):
while min(*pil_image.size) >= 2 * max_image_size:
pil_image = pil_image.resize(
tuple(x // 2 for x in pil_image.size), resample=Image.BOX
tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS
)
if max(*pil_image.size) > max_image_size:
scale = max_image_size / max(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS
)
if min(*pil_image.size) < 16:
scale = 16 / min(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS
)
arr = np.array(pil_image)
+1 -1
View File
@@ -103,7 +103,7 @@ def setup_model(dirname):
restored_img = self.face_helper.paste_faces_to_input_image()
restored_img = restored_img[:, :, ::-1]
if original_resolution != restored_img.shape[0:2]:
restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR)
restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LANCZOS4)
self.face_helper.clean_all()
if shared.opts.detailer_unload:
self.send_model_to(devices.cpu)
+127
View File
@@ -0,0 +1,127 @@
import numpy as np
def DetectDirect(A, dcc_type, k, T):
if dcc_type == 1:
# 45 degree diagonal direction
t1 = abs(A[2,0]-A[0,2])
t2 = abs(A[4,0]-A[2,2])+abs(A[2,2]-A[0,4])
t3 = abs(A[6,0]-A[4,2])+abs(A[4,2]-A[2,4])+abs(A[2,4]-A[0,6])
t4 = abs(A[6,2]-A[4,4])+abs(A[4,4]-A[2,6])
t5 = abs(A[6,4]-A[4,6])
d1 = t1+t2+t3+t4+t5
# 135 degree diagonal direction
t1 = abs(A[0,4]-A[2,6])
t2 = abs(A[0,2]-A[2,4])+abs(A[2,4]-A[4,6])
t3 = abs(A[0,0]-A[2,2])+abs(A[2,2]-A[4,4])+abs(A[4,4]-A[6,6])
t4 = abs(A[2,0]-A[4,2])+abs(A[4,2]-A[6,4])
t5 = abs(A[4,0]-A[6,2])
d2 = t1+t2+t3+t4+t5
else:
# horizontal direction
t1 = abs(A[0,1]-A[0,3])+abs(A[2,1]-A[2,3])+abs(A[4,1]-A[4,3])
t2 = abs(A[1,0]-A[1,2])+abs(A[1,2]-A[1,4])
t3 = abs(A[3,0]-A[3,2])+abs(A[3,2]-A[3,4])
d1 = t1+t2+t3
# vertical direction
t1 = abs(A[1,0]-A[3,0])+abs(A[1,2]-A[3,2])+abs(A[1,4]-A[3,4])
t2 = abs(A[0,1]-A[2,1])+abs(A[2,1]-A[4,1])
t3 = abs(A[0,3]-A[2,3])+abs(A[2,3]-A[4,3])
d2 = t1+t2+t3
# Compute the weight vector
w = np.array([1/(1+d1**k), 1/(1+d2**k)])
# Compute the directional index
n = 3
if (1+d1)/(1+d2) > T:
n = 1
elif (1+d2)/(1+d1) > T:
n = 2
return w, n
def PixelValue(A, mode, w, n, f):
if mode == 1:
v1 = np.diag(np.fliplr(A))[::2]
v2 = np.diag(A)[::2]
else:
v1 = A[3,::2]
v2 = A[::2,3]
if n == 1:
p = np.dot(v2, f)
elif n == 2:
p = np.dot(v1, f)
else:
p1 = np.dot(v1, f)
p2 = np.dot(v2, f)
p = (w[0]*p1+w[1]*p2)/(w[0]+w[1])
return p
def PadLeftTop(img_pad, H, W):
img = img_pad[3:-3,3:-3]
# Pad the first/last three col and row
img_pad[3:H+3,1]=img[:,0]
img_pad[H+3::2,3:W+3]=img[H-2:H-1,:]
img_pad[3:H+3,W+3::2]=img[:,W-2:W-1]
img_pad[1,3:W+3]=img[0,:]
# Pad the missing nine points
img_pad[1,1]=img[0,0]
img_pad[H+3::2,1]=img[H-2,0]
img_pad[H+3::2,W+3::2]=img[H-2,W-2]
img_pad[1,W+3::2]=img[0,W-2]
return img_pad
def PadRightBottom(img_pad, H, W):
img = img_pad[3:-3,3:-3]
# Pad the first/last three col and row
img_pad[3:H+3,0:3:2]=img[:,1:2]
img_pad[H+4::2,3:W+3]=img[H-1:H,:]
img_pad[3:H+3,W+4::2]=img[:,W-1:W]
img_pad[0:3:2,3:W+3]=img[1,:]
# Pad the missing nine points
img_pad[0:3:2,0:3:2]=img[1,1]
img_pad[H+4,0:3:2]=img[H-1,1]
img_pad[H+4,W+4]=img[H-1,W-1]
img_pad[0:3:2,W+4]=img[0,W-1]
return img_pad
def _DCC(I, k, T):
m, n = I.shape
nRow = 2*m
nCol = 2*n
A = np.zeros([nRow+6, nCol+6])
A[0+3:-1-3:2, 0+3:-1-3:2] = I
A = PadLeftTop(A, nRow, nCol)
f = np.array([-1, 9, 9, -1])/16
for i in range(4,nRow+3,2):
for j in range(4,nCol+3,2):
[w,n] = DetectDirect(A[i-3:i+4,j-3:j+4],1,k,T)
A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],1,w,n,f)
A = PadRightBottom(A, nRow, nCol)
for i in range(3,nRow+3,2):
for j in range(4,nCol+3,2):
[w,n] = DetectDirect(A[i-2:i+3,j-2:j+3],2,k,T)
A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],2,w,n,f)
for i in range(4,nRow+3,2):
for j in range(3,nCol+3,2):
[w,n] = DetectDirect(A[i-2:i+3,j-2:j+3],3,k,T)
A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],3,w,n,f)
return A[3:-3,3:-3]
'''
img: Shape[H,W,C], Value Range[0-1]
level: super resolution level
Return: super resolution img who shape is the same with input
'''
def DCC(img, level):
# hyper parameters
k, T = 5, 1.15
sr_img = img
# get the high resolution image channel by channel
for channel in range(img.shape[-1]):
sr_img_simple = img[:,:,channel]
for _ in range(level):
sr_img_simple = _DCC(sr_img_simple, k, T)
sr_img[:,:,channel] = sr_img_simple
return sr_img
+1 -1
View File
@@ -253,7 +253,7 @@ class RealESRGANer():
output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY)
else: # use the cv2 resize for alpha channel
h, w = alpha.shape[0:2]
output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR)
output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LANCZOS4)
# merge the alpha channel
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA)
+2 -2
View File
@@ -34,7 +34,7 @@ def restore(np_image, name, session, strength): # pylint: disable=unused-argumen
detected_faces = len(face_helper.cropped_faces)
for cropped_face in face_helper.cropped_faces:
cropped_face = cv2.resize(cropped_face, resolution, interpolation=cv2.INTER_LINEAR)
cropped_face = cv2.resize(cropped_face, resolution, interpolation=cv2.INTER_LANCZOS4)
cropped_face = cropped_face.astype(np.float16)[:,:,::-1] / 255.0
cropped_face = cropped_face.transpose((2, 0, 1))
cropped_face = (cropped_face - 0.5) / 0.5
@@ -52,7 +52,7 @@ def restore(np_image, name, session, strength): # pylint: disable=unused-argumen
restored_img = face_helper.paste_faces_to_input_image()
restored_img = restored_img[:, :, ::-1]
if original_resolution != restored_img.shape[0:2]:
restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR)
restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LANCZOS4)
face_helper.clean_all()
t1 = time.time()
+2 -2
View File
@@ -346,10 +346,10 @@ class YoloRestorer(Detailer):
with gr.Row():
enabled = gr.Checkbox(label="Enable detailer pass", elem_id=f"{tab}_detailer_enabled", value=False)
with gr.Row():
detailers = gr.Dropdown(label="Detailers", elem_id=f"{tab}_detailers", choices=self.list, value=shared.opts.detailer_models, multiselect=True)
detailers = gr.Dropdown(label="Detailer models", elem_id=f"{tab}_detailers", choices=self.list, value=shared.opts.detailer_models, multiselect=True)
ui_common.create_refresh_button(detailers, self.enumerate, {}, elem_id=f"{tab}_detailers_refresh")
with gr.Row():
classes = gr.Textbox(label="Classes", placeholder="Classes", elem_id=f"{tab}_detailer_classes")
classes = gr.Textbox(label="Detailer classes", placeholder="Classes", elem_id=f"{tab}_detailer_classes")
with gr.Row():
prompt = gr.Textbox(label="Detailer prompt", value='', placeholder='Detailer prompt', lines=2, elem_id=f"{tab}_detailer_prompt")
with gr.Row():
+7 -5
View File
@@ -260,9 +260,10 @@ def process_init(p: StableDiffusionProcessing):
else:
p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))]
if reset_prompts:
p.all_prompts, p.all_negative_prompts = shared.prompt_styles.apply_styles_to_prompts(p.all_prompts, p.all_negative_prompts, p.styles, p.all_seeds)
p.prompts = p.all_prompts[p.iteration * p.batch_size:(p.iteration+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[p.iteration * p.batch_size:(p.iteration+1) * p.batch_size]
if not hasattr(p, 'keep_prompts'):
p.all_prompts, p.all_negative_prompts = shared.prompt_styles.apply_styles_to_prompts(p.all_prompts, p.all_negative_prompts, p.styles, p.all_seeds)
p.prompts = p.all_prompts[p.iteration * p.batch_size:(p.iteration+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[p.iteration * p.batch_size:(p.iteration+1) * p.batch_size]
p.prompts, _ = extra_networks.parse_prompts(p.prompts)
@@ -312,8 +313,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if shared.native:
from modules import ipadapter
ipadapter.apply(shared.sd_model, p)
p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size]
if not hasattr(p, 'keep_prompts'):
p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.seeds = p.all_seeds[n * p.batch_size:(n+1) * p.batch_size]
p.subseeds = p.all_subseeds[n * p.batch_size:(n+1) * p.batch_size]
if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner):

Some files were not shown because too many files have changed in this diff Show More