Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdb88d798d | |||
| d66fa38804 | |||
| d36b732f55 | |||
| 9d39380705 | |||
| ddb28b33a3 | |||
| 1c0a0c4c26 | |||
| 7dfe959f4b | |||
| 8f64dad282 | |||
| 821adc3041 | |||
| e2b177c508 | |||
| e837124f4b | |||
| 3fdc3cfbbf | |||
| e9809de651 | |||
| 61f6479ea9 | |||
| e84703b253 | |||
| e4aa0c362e | |||
| a183ea4ba7 | |||
| 6c7c176dc9 | |||
| e6a8d0b4e6 | |||
| db263df5d5 | |||
| d1998d747d | |||
| c0eaeb15af | |||
| 9bcfb92a00 | |||
| d74fc56fa5 | |||
| a44ed231c2 | |||
| daae17851a | |||
| ce19a7baef | |||
| 8d6e72dbfa | |||
| 6f4f6bff6b | |||
| 367b823466 | |||
| c8ac42aad1 | |||
| 449bc7bcf3 | |||
| 3810413c00 | |||
| f8f5d6cea2 | |||
| cde35bebe9 | |||
| 49fee7c8db | |||
| b5b1487f6a | |||
| 5cb567c138 | |||
| d212fb59fe | |||
| 71314e47b1 | |||
| ba2a737cce | |||
| 909c3dfe83 | |||
| 9d4fdc45d3 | |||
| 63fd38a04f | |||
| 50190ca669 | |||
| 0980fdfe8c | |||
| bba306d414 | |||
| a95326bec4 | |||
| 0f82948e4f | |||
| 8e1c3561be | |||
| ff6f4680c4 | |||
| 3fadb4fc85 | |||
| 592e40ebe9 | |||
| 4068429ac7 | |||
| ac8ffb34e3 | |||
| ef83f6831f | |||
| 600f339c4c | |||
| a976f4dff9 | |||
| c48b6bf6bd | |||
| 2580235c72 | |||
| d9708c92b4 | |||
| e3aabe6959 | |||
| 1e1176b6eb | |||
| 219e64489c | |||
| 47ed9b2d39 | |||
| 6efdfe3234 |
+36
-1
@@ -1,3 +1,39 @@
|
|||||||
|
## 1.9.3
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* fix get_crop_region_v2 ([#15594](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15594))
|
||||||
|
|
||||||
|
## 1.9.2
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* restore 1.8.0-style naming of scripts
|
||||||
|
|
||||||
|
## 1.9.1
|
||||||
|
|
||||||
|
### Minor:
|
||||||
|
* Add avif support ([#15582](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15582))
|
||||||
|
* Add filename patterns: `[sampler_scheduler]` and `[scheduler]` ([#15581](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15581))
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* undo adding scripts to sys.modules
|
||||||
|
* Add schedulers API endpoint ([#15577](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15577))
|
||||||
|
* Remove API upscaling factor limits ([#15560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15560))
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* Fix images do not match / Coordinate 'right' is less than 'left' ([#15534](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15534))
|
||||||
|
* fix: remove_callbacks_for_function should also remove from the ordered map ([#15533](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15533))
|
||||||
|
* fix x1 upscalers ([#15555](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15555))
|
||||||
|
* Fix cls.__module__ value in extension script ([#15532](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15532))
|
||||||
|
* fix typo in function call (eror -> error) ([#15531](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15531))
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
* Hide 'No Image data blocks found.' message ([#15567](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15567))
|
||||||
|
* Allow webui.sh to be runnable from arbitrary directories containing a .git file ([#15561](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15561))
|
||||||
|
* Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ ([#15544](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15544))
|
||||||
|
* numpy DeprecationWarning product -> prod ([#15547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15547))
|
||||||
|
* get_crop_region_v2 ([#15583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15583), [#15587](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15587))
|
||||||
|
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
@@ -85,7 +121,6 @@
|
|||||||
* Fix extra-single-image API not doing upscale failed ([#15465](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15465))
|
* Fix extra-single-image API not doing upscale failed ([#15465](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15465))
|
||||||
* error handling paste_field callables ([#15470](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15470))
|
* error handling paste_field callables ([#15470](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15470))
|
||||||
|
|
||||||
|
|
||||||
### Hardware:
|
### Hardware:
|
||||||
* Add training support and change lspci for Ascend NPU ([#14981](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14981))
|
* Add training support and change lspci for Ascend NPU ([#14981](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14981))
|
||||||
* Update to ROCm5.7 and PyTorch ([#14820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14820))
|
* Update to ROCm5.7 and PyTorch ([#14820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14820))
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ function extraNetworksShowMetadata(text) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.eror(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
var elem = document.createElement('pre');
|
var elem = document.createElement('pre');
|
||||||
|
|||||||
+13
-1
@@ -17,7 +17,7 @@ from fastapi.encoders import jsonable_encoder
|
|||||||
from secrets import compare_digest
|
from secrets import compare_digest
|
||||||
|
|
||||||
import modules.shared as shared
|
import modules.shared as shared
|
||||||
from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models
|
from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models, sd_schedulers
|
||||||
from modules.api import models
|
from modules.api import models
|
||||||
from modules.shared import opts
|
from modules.shared import opts
|
||||||
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
|
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
|
||||||
@@ -221,6 +221,7 @@ class Api:
|
|||||||
self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
|
self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
|
||||||
self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
|
self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
|
||||||
self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem])
|
self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem])
|
||||||
|
self.add_api_route("/sdapi/v1/schedulers", self.get_schedulers, methods=["GET"], response_model=list[models.SchedulerItem])
|
||||||
self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem])
|
self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem])
|
||||||
self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem])
|
self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem])
|
||||||
self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem])
|
self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem])
|
||||||
@@ -683,6 +684,17 @@ class Api:
|
|||||||
def get_samplers(self):
|
def get_samplers(self):
|
||||||
return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers]
|
return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers]
|
||||||
|
|
||||||
|
def get_schedulers(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": scheduler.name,
|
||||||
|
"label": scheduler.label,
|
||||||
|
"aliases": scheduler.aliases,
|
||||||
|
"default_rho": scheduler.default_rho,
|
||||||
|
"need_inner_model": scheduler.need_inner_model,
|
||||||
|
}
|
||||||
|
for scheduler in sd_schedulers.schedulers]
|
||||||
|
|
||||||
def get_upscalers(self):
|
def get_upscalers(self):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class ExtrasBaseRequest(BaseModel):
|
|||||||
gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.")
|
gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.")
|
||||||
codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.")
|
codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.")
|
||||||
codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.")
|
codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.")
|
||||||
upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.")
|
upscaling_resize: float = Field(default=2, title="Upscaling Factor", gt=0, description="By how much to upscale the image, only used when resize_mode=0.")
|
||||||
upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.")
|
upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.")
|
||||||
upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.")
|
upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.")
|
||||||
upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?")
|
upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?")
|
||||||
@@ -235,6 +235,13 @@ class SamplerItem(BaseModel):
|
|||||||
aliases: list[str] = Field(title="Aliases")
|
aliases: list[str] = Field(title="Aliases")
|
||||||
options: dict[str, str] = Field(title="Options")
|
options: dict[str, str] = Field(title="Options")
|
||||||
|
|
||||||
|
class SchedulerItem(BaseModel):
|
||||||
|
name: str = Field(title="Name")
|
||||||
|
label: str = Field(title="Label")
|
||||||
|
aliases: Optional[list[str]] = Field(title="Aliases")
|
||||||
|
default_rho: Optional[float] = Field(title="Default Rho")
|
||||||
|
need_inner_model: Optional[bool] = Field(title="Needs Inner Model")
|
||||||
|
|
||||||
class UpscalerItem(BaseModel):
|
class UpscalerItem(BaseModel):
|
||||||
name: str = Field(title="Name")
|
name: str = Field(title="Name")
|
||||||
model_name: Optional[str] = Field(title="Model Name")
|
model_name: Optional[str] = Field(title="Model Name")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import tqdm
|
|||||||
from einops import rearrange, repeat
|
from einops import rearrange, repeat
|
||||||
from ldm.util import default
|
from ldm.util import default
|
||||||
from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors
|
from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors
|
||||||
from modules.textual_inversion import textual_inversion, logging
|
from modules.textual_inversion import textual_inversion, saving_settings
|
||||||
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
||||||
from torch import einsum
|
from torch import einsum
|
||||||
from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_
|
from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_
|
||||||
@@ -533,7 +533,7 @@ def train_hypernetwork(id_task, hypernetwork_name: str, learn_rate: float, batch
|
|||||||
model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds),
|
model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds),
|
||||||
**{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]}
|
**{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]}
|
||||||
)
|
)
|
||||||
logging.save_settings_to_file(log_directory, {**saved_params, **locals()})
|
saving_settings.save_settings_to_file(log_directory, {**saved_params, **locals()})
|
||||||
|
|
||||||
latent_sampling_method = ds.latent_sampling_method
|
latent_sampling_method = ds.latent_sampling_method
|
||||||
|
|
||||||
|
|||||||
+41
-2
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
import pytz
|
import pytz
|
||||||
import io
|
import io
|
||||||
import math
|
import math
|
||||||
@@ -13,6 +13,8 @@ import numpy as np
|
|||||||
import piexif
|
import piexif
|
||||||
import piexif.helper
|
import piexif.helper
|
||||||
from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps
|
from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps
|
||||||
|
# pillow_avif needs to be imported somewhere in code for it to work
|
||||||
|
import pillow_avif # noqa: F401
|
||||||
import string
|
import string
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -347,6 +349,32 @@ def sanitize_filename_part(text, replace_spaces=True):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_scheduler_str(sampler_name, scheduler_name):
|
||||||
|
"""Returns {Scheduler} if the scheduler is applicable to the sampler"""
|
||||||
|
if scheduler_name == 'Automatic':
|
||||||
|
config = sd_samplers.find_sampler_config(sampler_name)
|
||||||
|
scheduler_name = config.options.get('scheduler', 'Automatic')
|
||||||
|
return scheduler_name.capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_sampler_scheduler_str(sampler_name, scheduler_name):
|
||||||
|
"""Returns the '{Sampler} {Scheduler}' if the scheduler is applicable to the sampler"""
|
||||||
|
return f'{sampler_name} {get_scheduler_str(sampler_name, scheduler_name)}'
|
||||||
|
|
||||||
|
|
||||||
|
def get_sampler_scheduler(p, sampler):
|
||||||
|
"""Returns '{Sampler} {Scheduler}' / '{Scheduler}' / 'NOTHING_AND_SKIP_PREVIOUS_TEXT'"""
|
||||||
|
if hasattr(p, 'scheduler') and hasattr(p, 'sampler_name'):
|
||||||
|
if sampler:
|
||||||
|
sampler_scheduler = get_sampler_scheduler_str(p.sampler_name, p.scheduler)
|
||||||
|
else:
|
||||||
|
sampler_scheduler = get_scheduler_str(p.sampler_name, p.scheduler)
|
||||||
|
return sanitize_filename_part(sampler_scheduler, replace_spaces=False)
|
||||||
|
return NOTHING_AND_SKIP_PREVIOUS_TEXT
|
||||||
|
|
||||||
|
|
||||||
class FilenameGenerator:
|
class FilenameGenerator:
|
||||||
replacements = {
|
replacements = {
|
||||||
'seed': lambda self: self.seed if self.seed is not None else '',
|
'seed': lambda self: self.seed if self.seed is not None else '',
|
||||||
@@ -358,6 +386,8 @@ class FilenameGenerator:
|
|||||||
'height': lambda self: self.image.height,
|
'height': lambda self: self.image.height,
|
||||||
'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False),
|
'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False),
|
||||||
'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False),
|
'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False),
|
||||||
|
'sampler_scheduler': lambda self: self.p and get_sampler_scheduler(self.p, True),
|
||||||
|
'scheduler': lambda self: self.p and get_sampler_scheduler(self.p, False),
|
||||||
'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash),
|
'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash),
|
||||||
'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False),
|
'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False),
|
||||||
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
|
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
|
||||||
@@ -569,6 +599,16 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p
|
|||||||
})
|
})
|
||||||
|
|
||||||
piexif.insert(exif_bytes, filename)
|
piexif.insert(exif_bytes, filename)
|
||||||
|
elif extension.lower() == '.avif':
|
||||||
|
if opts.enable_pnginfo and geninfo is not None:
|
||||||
|
exif_bytes = piexif.dump({
|
||||||
|
"Exif": {
|
||||||
|
piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or "", encoding="unicode")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
image.save(filename,format=image_format, exif=exif_bytes)
|
||||||
elif extension.lower() == ".gif":
|
elif extension.lower() == ".gif":
|
||||||
image.save(filename, format=image_format, comment=geninfo)
|
image.save(filename, format=image_format, comment=geninfo)
|
||||||
else:
|
else:
|
||||||
@@ -747,7 +787,6 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]:
|
|||||||
exif_comment = exif_comment.decode('utf8', errors="ignore")
|
exif_comment = exif_comment.decode('utf8', errors="ignore")
|
||||||
|
|
||||||
if exif_comment:
|
if exif_comment:
|
||||||
items['exif comment'] = exif_comment
|
|
||||||
geninfo = exif_comment
|
geninfo = exif_comment
|
||||||
elif "comment" in items: # for gif
|
elif "comment" in items: # for gif
|
||||||
geninfo = items["comment"].decode('utf8', errors="ignore")
|
geninfo = items["comment"].decode('utf8', errors="ignore")
|
||||||
|
|||||||
+32
-10
@@ -1,17 +1,39 @@
|
|||||||
from PIL import Image, ImageFilter, ImageOps
|
from PIL import Image, ImageFilter, ImageOps
|
||||||
|
|
||||||
|
|
||||||
def get_crop_region(mask, pad=0):
|
def get_crop_region_v2(mask, pad=0):
|
||||||
"""finds a rectangular region that contains all masked ares in an image. Returns (x1, y1, x2, y2) coordinates of the rectangle.
|
"""
|
||||||
For example, if a user has painted the top-right part of a 512x512 image, the result may be (256, 0, 512, 256)"""
|
Finds a rectangular region that contains all masked ares in a mask.
|
||||||
mask_img = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
Returns None if mask is completely black mask (all 0)
|
||||||
box = mask_img.getbbox()
|
|
||||||
if box:
|
Parameters:
|
||||||
|
mask: PIL.Image.Image L mode or numpy 1d array
|
||||||
|
pad: int number of pixels that the region will be extended on all sides
|
||||||
|
Returns: (x1, y1, x2, y2) | None
|
||||||
|
|
||||||
|
Introduced post 1.9.0
|
||||||
|
"""
|
||||||
|
mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
||||||
|
if box := mask.getbbox():
|
||||||
x1, y1, x2, y2 = box
|
x1, y1, x2, y2 = box
|
||||||
else: # when no box is found
|
return (max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])) if pad else box
|
||||||
x1, y1 = mask_img.size
|
|
||||||
x2 = y2 = 0
|
|
||||||
return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1])
|
def get_crop_region(mask, pad=0):
|
||||||
|
"""
|
||||||
|
Same function as get_crop_region_v2 but handles completely black mask (all 0) differently
|
||||||
|
when mask all black still return coordinates but the coordinates may be invalid ie x2>x1 or y2>y1
|
||||||
|
Notes: it is possible for the coordinates to be "valid" again if pad size is sufficiently large
|
||||||
|
(mask_size.x-pad, mask_size.y-pad, pad, pad)
|
||||||
|
|
||||||
|
Extension developer should use get_crop_region_v2 instead unless for compatibility considerations.
|
||||||
|
"""
|
||||||
|
mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
||||||
|
if box := get_crop_region_v2(mask, pad):
|
||||||
|
return box
|
||||||
|
x1, y1 = mask.size
|
||||||
|
x2 = y2 = 0
|
||||||
|
return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])
|
||||||
|
|
||||||
|
|
||||||
def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height):
|
def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import os
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from modules import shared, images, devices, scripts, scripts_postprocessing, ui_common, infotext_utils
|
from modules import shared, images, devices, scripts, scripts_postprocessing, ui_common, infotext_utils, util
|
||||||
from modules.shared import opts
|
from modules.shared import opts
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +31,8 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
|
|||||||
for filename in image_list:
|
for filename in image_list:
|
||||||
yield filename, filename
|
yield filename, filename
|
||||||
else:
|
else:
|
||||||
|
if isinstance(image, str):
|
||||||
|
image = util.decode_base64_to_image(image)
|
||||||
assert image, 'image not selected'
|
assert image, 'image not selected'
|
||||||
yield image, None
|
yield image, None
|
||||||
|
|
||||||
@@ -62,11 +64,13 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
|
|||||||
else:
|
else:
|
||||||
image_data = image_placeholder
|
image_data = image_placeholder
|
||||||
|
|
||||||
|
image_data = image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB")
|
||||||
|
|
||||||
parameters, existing_pnginfo = images.read_info_from_image(image_data)
|
parameters, existing_pnginfo = images.read_info_from_image(image_data)
|
||||||
if parameters:
|
if parameters:
|
||||||
existing_pnginfo["parameters"] = parameters
|
existing_pnginfo["parameters"] = parameters
|
||||||
|
|
||||||
initial_pp = scripts_postprocessing.PostprocessedImage(image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB"))
|
initial_pp = scripts_postprocessing.PostprocessedImage(image_data)
|
||||||
|
|
||||||
scripts.scripts_postproc.run(initial_pp, args)
|
scripts.scripts_postproc.run(initial_pp, args)
|
||||||
|
|
||||||
|
|||||||
+19
-10
@@ -1611,16 +1611,23 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
|||||||
if self.inpaint_full_res:
|
if self.inpaint_full_res:
|
||||||
self.mask_for_overlay = image_mask
|
self.mask_for_overlay = image_mask
|
||||||
mask = image_mask.convert('L')
|
mask = image_mask.convert('L')
|
||||||
crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding)
|
crop_region = masking.get_crop_region_v2(mask, self.inpaint_full_res_padding)
|
||||||
crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
|
if crop_region:
|
||||||
x1, y1, x2, y2 = crop_region
|
crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
|
||||||
|
x1, y1, x2, y2 = crop_region
|
||||||
mask = mask.crop(crop_region)
|
mask = mask.crop(crop_region)
|
||||||
image_mask = images.resize_image(2, mask, self.width, self.height)
|
image_mask = images.resize_image(2, mask, self.width, self.height)
|
||||||
self.paste_to = (x1, y1, x2-x1, y2-y1)
|
self.paste_to = (x1, y1, x2-x1, y2-y1)
|
||||||
|
self.extra_generation_params["Inpaint area"] = "Only masked"
|
||||||
self.extra_generation_params["Inpaint area"] = "Only masked"
|
self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding
|
||||||
self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding
|
else:
|
||||||
|
crop_region = None
|
||||||
|
image_mask = None
|
||||||
|
self.mask_for_overlay = None
|
||||||
|
self.inpaint_full_res = False
|
||||||
|
massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.'
|
||||||
|
model_hijack.comments.append(massage)
|
||||||
|
logging.info(massage)
|
||||||
else:
|
else:
|
||||||
image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height)
|
image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height)
|
||||||
np_mask = np.array(image_mask)
|
np_mask = np.array(image_mask)
|
||||||
@@ -1648,6 +1655,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
|||||||
image = images.resize_image(self.resize_mode, image, self.width, self.height)
|
image = images.resize_image(self.resize_mode, image, self.width, self.height)
|
||||||
|
|
||||||
if image_mask is not None:
|
if image_mask is not None:
|
||||||
|
if self.mask_for_overlay.size != (image.width, image.height):
|
||||||
|
self.mask_for_overlay = images.resize_image(self.resize_mode, self.mask_for_overlay, image.width, image.height)
|
||||||
image_masked = Image.new('RGBa', (image.width, image.height))
|
image_masked = Image.new('RGBa', (image.width, image.height))
|
||||||
image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L')))
|
image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L')))
|
||||||
|
|
||||||
|
|||||||
@@ -448,6 +448,9 @@ def remove_callbacks_for_function(callback_func):
|
|||||||
for callback_list in callback_map.values():
|
for callback_list in callback_map.values():
|
||||||
for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]:
|
for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]:
|
||||||
callback_list.remove(callback_to_remove)
|
callback_list.remove(callback_to_remove)
|
||||||
|
for ordered_callback_list in ordered_callbacks_map.values():
|
||||||
|
for callback_to_remove in [cb for cb in ordered_callback_list if cb.callback == callback_func]:
|
||||||
|
ordered_callback_list.remove(callback_to_remove)
|
||||||
|
|
||||||
|
|
||||||
def on_app_started(callback, *, name=None):
|
def on_app_started(callback, *, name=None):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import os
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
|
|
||||||
from modules import errors
|
from modules import errors
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
loaded_scripts = {}
|
loaded_scripts = {}
|
||||||
@@ -14,10 +13,6 @@ def load_module(path):
|
|||||||
module_spec.loader.exec_module(module)
|
module_spec.loader.exec_module(module)
|
||||||
|
|
||||||
loaded_scripts[path] = module
|
loaded_scripts[path] = module
|
||||||
|
|
||||||
module_name, _ = os.path.splitext(os.path.basename(path))
|
|
||||||
sys.modules["scripts." + module_name] = module
|
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import os.path
|
||||||
import warnings
|
import warnings
|
||||||
|
import logging
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import zlib
|
import zlib
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EmbeddingEncoder(json.JSONEncoder):
|
class EmbeddingEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
@@ -43,7 +47,7 @@ def lcg(m=2**32, a=1664525, c=1013904223, seed=0):
|
|||||||
|
|
||||||
def xor_block(block):
|
def xor_block(block):
|
||||||
g = lcg()
|
g = lcg()
|
||||||
randblock = np.array([next(g) for _ in range(np.product(block.shape))]).astype(np.uint8).reshape(block.shape)
|
randblock = np.array([next(g) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape)
|
||||||
return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F)
|
return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F)
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +118,7 @@ def extract_image_data_embed(image):
|
|||||||
outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F
|
outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F
|
||||||
black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0)
|
black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0)
|
||||||
if black_cols[0].shape[0] < 2:
|
if black_cols[0].shape[0] < 2:
|
||||||
print('No Image data blocks found.')
|
logger.debug(f'{os.path.basename(getattr(image, "filename", "unknown image file"))}: no embedded information found.')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8)
|
data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import modules.textual_inversion.dataset
|
|||||||
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
||||||
|
|
||||||
from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay
|
from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay
|
||||||
from modules.textual_inversion.logging import save_settings_to_file
|
from modules.textual_inversion.saving_settings import save_settings_to_file
|
||||||
|
|
||||||
|
|
||||||
TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"])
|
TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"])
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ def create_ui():
|
|||||||
output_panel.html_log,
|
output_panel.html_log,
|
||||||
],
|
],
|
||||||
show_progress=False,
|
show_progress=False,
|
||||||
|
preprocess=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
parameters_copypaste.add_paste_fields("extras", extras_image, None)
|
parameters_copypaste.add_paste_fields("extras", extras_image, None)
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ class Upscaler:
|
|||||||
dest_h = int((img.height * scale) // 8 * 8)
|
dest_h = int((img.height * scale) // 8 * 8)
|
||||||
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
if img.width >= dest_w and img.height >= dest_h:
|
if img.width >= dest_w and img.height >= dest_h and scale != 1:
|
||||||
break
|
break
|
||||||
|
|
||||||
if shared.state.interrupted:
|
if shared.state.interrupted:
|
||||||
|
|||||||
@@ -211,3 +211,13 @@ Requested path was: {path}
|
|||||||
subprocess.Popen(["wsl-open", path])
|
subprocess.Popen(["wsl-open", path])
|
||||||
else:
|
else:
|
||||||
subprocess.Popen(["xdg-open", path])
|
subprocess.Popen(["xdg-open", path])
|
||||||
|
|
||||||
|
|
||||||
|
def decode_base64_to_image(base64_str: str):
|
||||||
|
from modules import images
|
||||||
|
from io import BytesIO
|
||||||
|
import base64
|
||||||
|
if base64_str.startswith("data:image/"):
|
||||||
|
base64_str = base64_str.partition(';')[2].partition(',')[2]
|
||||||
|
image = images.read(BytesIO(base64.b64decode(base64_str)))
|
||||||
|
return image
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ torch
|
|||||||
torchdiffeq
|
torchdiffeq
|
||||||
torchsde
|
torchsde
|
||||||
transformers==4.30.2
|
transformers==4.30.2
|
||||||
|
pillow-avif-plugin==1.4.3
|
||||||
@@ -29,3 +29,4 @@ torchdiffeq==0.2.3
|
|||||||
torchsde==0.2.6
|
torchsde==0.2.6
|
||||||
transformers==4.30.2
|
transformers==4.30.2
|
||||||
httpx==0.24.1
|
httpx==0.24.1
|
||||||
|
pillow-avif-plugin==1.4.3
|
||||||
|
|||||||
@@ -113,13 +113,13 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -d .git ]]
|
if [[ -d "$SCRIPT_DIR/.git" ]]
|
||||||
then
|
then
|
||||||
printf "\n%s\n" "${delimiter}"
|
printf "\n%s\n" "${delimiter}"
|
||||||
printf "Repo already cloned, using it as install directory"
|
printf "Repo already cloned, using it as install directory"
|
||||||
printf "\n%s\n" "${delimiter}"
|
printf "\n%s\n" "${delimiter}"
|
||||||
install_dir="${PWD}/../"
|
install_dir="${SCRIPT_DIR}/../"
|
||||||
clone_dir="${PWD##*/}"
|
clone_dir="${SCRIPT_DIR##*/}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check prerequisites
|
# Check prerequisites
|
||||||
@@ -243,7 +243,7 @@ prepare_tcmalloc() {
|
|||||||
for lib in "${TCMALLOC_LIBS[@]}"
|
for lib in "${TCMALLOC_LIBS[@]}"
|
||||||
do
|
do
|
||||||
# Determine which type of tcmalloc library the library supports
|
# Determine which type of tcmalloc library the library supports
|
||||||
TCMALLOC="$(PATH=/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)"
|
TCMALLOC="$(PATH=/sbin:/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)"
|
||||||
TC_INFO=(${TCMALLOC//=>/})
|
TC_INFO=(${TCMALLOC//=>/})
|
||||||
if [[ ! -z "${TC_INFO}" ]]; then
|
if [[ ! -z "${TC_INFO}" ]]; then
|
||||||
echo "Check TCMalloc: ${TC_INFO}"
|
echo "Check TCMalloc: ${TC_INFO}"
|
||||||
|
|||||||
Reference in New Issue
Block a user