mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
+5
-2
@@ -1,6 +1,6 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2026-07-30
|
||||
## Update for 2026-07-20
|
||||
|
||||
- **Compute**
|
||||
- torch: update to `2.13.0` for CUDA, ROCm, IPEX
|
||||
@@ -11,8 +11,11 @@
|
||||
- sdnq attention optimizations
|
||||
- sdnq separate dit/te settings
|
||||
- **Features**
|
||||
- process: read video properties and metadata
|
||||
- process: allow processing of video files
|
||||
*note*: currently only seedvr postprocessing is supported
|
||||
other workflows will be added in future releases
|
||||
- seedvr: enhanced upscaler support
|
||||
- process: read video properties metadata
|
||||
- logs: propagate server tracebacks to client
|
||||
- networks: improve search and filtering to allow multi-words
|
||||
- **Fixes**
|
||||
|
||||
Submodule extensions-builtin/sdnext-modernui updated: 9868ac66d0...820a266789
@@ -307,6 +307,8 @@ class Detailer():
|
||||
via detailer_opt(). The seed is resolved here so restore()'s inpaint passes are reproducible and the
|
||||
effective value can be reported back.
|
||||
"""
|
||||
if image is None:
|
||||
return None
|
||||
from modules.processing_helpers import get_fixed_seed
|
||||
from modules.paths import resolve_output_path
|
||||
seed = int(get_fixed_seed(seed))
|
||||
|
||||
@@ -4,10 +4,11 @@ import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from modules import devices
|
||||
from modules.shared import opts, log
|
||||
from modules.shared import opts
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules.image import convert
|
||||
from modules.model_quant import do_post_load_quant
|
||||
from modules.logger import log, console
|
||||
|
||||
|
||||
MODELS_MAP = {
|
||||
@@ -31,6 +32,12 @@ class UpscalerSeedVR(Upscaler):
|
||||
self.tile_size = 1024
|
||||
self.tile_overlap = 0.25
|
||||
self.device = devices.device
|
||||
self.step = 1
|
||||
self.frames = 0
|
||||
self.offload = True
|
||||
self.pbar = None
|
||||
self.task = None
|
||||
self.fps = 24
|
||||
|
||||
def load_model(self, path: str):
|
||||
model_name = MODELS_MAP.get(path, None)
|
||||
@@ -71,16 +78,15 @@ class UpscalerSeedVR(Upscaler):
|
||||
log.info(f'Upscaler loaded: name="{self.name}" model="{model_name}" time={t1 - t0:.2f}')
|
||||
|
||||
def vae_encode(self, samples):
|
||||
log.debug(f'Upscaler encode: samples={samples[0].shape if len(samples) > 0 else None} tile={self.model.vae.tile_sample_min_size} overlap={self.model.vae.tile_overlap_factor}')
|
||||
latents = []
|
||||
if len(samples) == 0:
|
||||
return latents
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
self.model.vae = self.model.vae.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
self.pbar.update(self.task, description=f'encode: samples={samples[0].shape if len(samples) > 0 else None} tile={self.model.vae.tile_sample_min_size} overlap={self.model.vae.tile_overlap_factor}')
|
||||
if self.offload:
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
self.model.vae = self.model.vae.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
from einops import rearrange
|
||||
from modules.seedvr.src.optimization import memory_manager
|
||||
memory_manager.clear_rope_cache(self.model)
|
||||
scale = self.model.config.vae.scaling_factor
|
||||
shift = self.model.config.vae.get("shifting_factor", 0.0)
|
||||
batches = [sample.unsqueeze(0) for sample in samples]
|
||||
@@ -93,21 +99,21 @@ class UpscalerSeedVR(Upscaler):
|
||||
latent = (latent - shift) * scale
|
||||
latents.append(latent)
|
||||
latents = [latent.squeeze(0) for latent in latents]
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
if self.offload:
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
return latents
|
||||
|
||||
def vae_decode(self, latents, target_dtype: torch.dtype = None):
|
||||
log.debug(f'Upscaler decode: latents={latents[0].shape if len(latents) > 0 else None} tile={self.model.vae.tile_latent_min_size} overlap={self.model.vae.tile_overlap_factor}')
|
||||
self.pbar.update(self.task, description=f'decode: latents={latents[0].shape if len(latents) > 0 else None} tile={self.model.vae.tile_latent_min_size} overlap={self.model.vae.tile_overlap_factor}')
|
||||
samples = []
|
||||
if len(latents) == 0:
|
||||
return samples
|
||||
from einops import rearrange
|
||||
from modules.seedvr.src.optimization import memory_manager
|
||||
memory_manager.clear_rope_cache(self.model)
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
self.model.vae = self.model.vae.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
if self.offload:
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
self.model.vae = self.model.vae.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
scale = self.model.config.vae.scaling_factor
|
||||
shift = self.model.config.vae.get("shifting_factor", 0.0)
|
||||
latents = [latent.unsqueeze(0) for latent in latents]
|
||||
@@ -121,28 +127,89 @@ class UpscalerSeedVR(Upscaler):
|
||||
sample = self.model.vae.postprocess(sample)
|
||||
samples.append(sample)
|
||||
samples = [sample.squeeze(0) for sample in samples]
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
if self.offload:
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
return samples
|
||||
|
||||
def model_step(self, *args, **kwargs):
|
||||
from modules.seedvr.src.core import generation
|
||||
from modules.seedvr.src.optimization import memory_manager
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
self.model.dit = self.model.dit.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
log.debug(f'Upscaler inference: args={len(args)} kwargs={list(kwargs.keys())}')
|
||||
memory_manager.preinitialize_rope_cache(self.model)
|
||||
if self.offload:
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
self.model.dit = self.model.dit.to(device=self.device)
|
||||
devices.torch_gc()
|
||||
with devices.inference_context():
|
||||
self.pbar.update(self.task, description=f'inference: step={self.step}')
|
||||
result = generation.generation_step_original(*args, **kwargs)
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
self.pbar.update(self.task, advance=self.step)
|
||||
if self.offload:
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
devices.torch_gc()
|
||||
return result
|
||||
|
||||
def do_upscale(self, img: Image.Image, selected_file, cfg_scale: float = 1.5, cfg_rescale: float = 0.0, steps: int = 1, seed: int = -1, scale: float | None = None, tile_size: int = 1024, tile_overlap: float = 0.25):
|
||||
def read_image(self, image: str | Image.Image):
|
||||
try:
|
||||
if isinstance(image, str):
|
||||
image = Image.open(image)
|
||||
image = image.convert("RGB")
|
||||
width = image.width
|
||||
tensor = np.array(image)
|
||||
tensor = torch.from_numpy(tensor).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) / 255.0
|
||||
self.frames = 1
|
||||
return tensor, width
|
||||
except Exception as e:
|
||||
log.error(f'Upscaler: name="SeedVR2" image="{image}" {e}')
|
||||
return None, None
|
||||
|
||||
def read_video(self, video_path: str):
|
||||
try:
|
||||
import cv2
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" failed to open')
|
||||
return None, None
|
||||
frames = []
|
||||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
frames.append(frame)
|
||||
cap.release()
|
||||
if len(frames) == 0:
|
||||
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" no frames read')
|
||||
return None, None
|
||||
tensor = torch.from_numpy(np.array(frames)).to(device=devices.device, dtype=devices.dtype) / 255.0
|
||||
self.frames = tensor.shape[0]
|
||||
self.fps = int(cap.get(cv2.CAP_PROP_FPS))
|
||||
return tensor, width
|
||||
except Exception as e:
|
||||
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" {e}')
|
||||
return None, None
|
||||
|
||||
def do_upscale(self,
|
||||
img: Image.Image | str,
|
||||
selected_file,
|
||||
cfg_scale: float = 1.5,
|
||||
cfg_rescale: float = 0.0,
|
||||
steps: int = 1,
|
||||
seed: int = -1,
|
||||
scale: float | None = None,
|
||||
tile_size: int = 1024,
|
||||
tile_overlap: float = 0.25,
|
||||
batch_size: int = 1,
|
||||
batch_overlap: int = 0,
|
||||
offload: bool = True
|
||||
):
|
||||
self.offload = offload
|
||||
self.load_model(selected_file)
|
||||
if self.model is None:
|
||||
return img
|
||||
if not self.offload:
|
||||
self.model.dit = self.model.dit.to(device=devices.device)
|
||||
self.model.vae = self.model.vae.to(device=devices.device)
|
||||
devices.torch_gc()
|
||||
|
||||
from modules.seedvr.src.core import generation
|
||||
|
||||
@@ -152,32 +219,55 @@ class UpscalerSeedVR(Upscaler):
|
||||
self.model.vae.tile_sample_min_size = self.tile_size
|
||||
self.model.vae.tile_latent_min_size = self.tile_size // 8
|
||||
self.model.vae.tile_overlap_factor = self.tile_overlap
|
||||
width = int(self.scale * img.width) // 8 * 8
|
||||
image_tensor = np.array(img)
|
||||
image_tensor = torch.from_numpy(image_tensor).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) / 255.0
|
||||
if isinstance(img, Image.Image):
|
||||
tensor, width = self.read_image(img)
|
||||
elif isinstance(img, str):
|
||||
tensor, width = self.read_video(img)
|
||||
else:
|
||||
log.error(f'Upscaler: name="SeedVR2" image="{img}" unsupported type {type(img)}')
|
||||
return img
|
||||
|
||||
if tensor is None or width is None:
|
||||
log.error(f'Upscaler: name="SeedVR2" image="{img}" failed to read')
|
||||
return img
|
||||
width = int(self.scale * width) // 8 * 8
|
||||
random.seed()
|
||||
seed = int(random.randrange(4294967294)) if seed == -1 else int(seed)
|
||||
self.step = 1 if self.frames == 1 else batch_size - batch_overlap
|
||||
|
||||
t0 = time.time()
|
||||
with devices.inference_context():
|
||||
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale}:{cfg_rescale} seed={seed} steps={steps} frames={self.frames} mode={"image" if self.frames == 1 else "video"} tile={self.tile_size}:{self.tile_overlap} batch={batch_size}:{batch_overlap} offload={self.offload}')
|
||||
import rich.progress as rp
|
||||
self.pbar = rp.Progress(rp.TextColumn('[cyan]SeedVR:'), rp.BarColumn(), rp.MofNCompleteColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=console)
|
||||
self.task = self.pbar.add_task(total=self.frames, description='starting...')
|
||||
with devices.inference_context(), self.pbar:
|
||||
self.pbar.update(self.task, description='initialize rope')
|
||||
from modules.seedvr.src.optimization import memory_manager
|
||||
memory_manager.clear_rope_cache(self.model)
|
||||
memory_manager.preinitialize_rope_cache(self.model)
|
||||
result_tensor = generation.generation_loop(
|
||||
runner=self.model,
|
||||
images=image_tensor,
|
||||
images=tensor,
|
||||
cfg_scale=cfg_scale,
|
||||
cfg_rescale=cfg_rescale,
|
||||
steps=steps,
|
||||
steps=steps, # TODO SeedVR steps
|
||||
batch_size=batch_size, # TODO SeedVR batch size
|
||||
temporal_overlap=batch_overlap, # TODO SeedVR temporal overlap
|
||||
seed=seed,
|
||||
res_w=width,
|
||||
batch_size=1,
|
||||
temporal_overlap=0,
|
||||
device=devices.device,
|
||||
)
|
||||
memory_manager.clear_rope_cache(self.model)
|
||||
|
||||
self.pbar.update(self.task, completed=self.frames)
|
||||
t1 = time.time()
|
||||
tiles = getattr(self.model.vae, "tiles", None)
|
||||
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale} seed={seed} tiles={tiles} time={t1 - t0:.2f}')
|
||||
img = convert.to_pil(result_tensor.squeeze())
|
||||
self.frames = result_tensor.shape[0] if result_tensor is not None else 0
|
||||
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale} seed={seed} tiles={tiles} frames={self.frames} time={t1 - t0:.2f}')
|
||||
|
||||
if self.offload:
|
||||
self.model.dit = self.model.dit.to(device="cpu")
|
||||
self.model.vae = self.model.vae.to(device="cpu")
|
||||
if opts.upscaler_unload:
|
||||
self.model.dit = None
|
||||
self.model.vae = None
|
||||
@@ -185,4 +275,15 @@ class UpscalerSeedVR(Upscaler):
|
||||
self.model = None
|
||||
log.debug(f'Upscaler unload: type="{self.name}" model="{selected_file}"')
|
||||
devices.torch_gc(force=True)
|
||||
return img
|
||||
|
||||
if self.frames == 1:
|
||||
img = convert.to_pil(result_tensor.squeeze())
|
||||
return img
|
||||
elif self.frames > 1:
|
||||
from modules.video_models.video_save import save_video
|
||||
pixels = result_tensor.permute(3, 0, 1, 2).unsqueeze(0) # from (t, h, w, c) to (n, c, t, h, w)
|
||||
_frames, filename, _thumb = save_video(p=None, pixels=pixels, mp4_fps=self.fps, mp4_thumb=False, mp4_frames=False, reclamp=False)
|
||||
return filename
|
||||
else:
|
||||
log.error(f'Upscaler: name="SeedVR2" model="{selected_file}" no frames generated')
|
||||
return img
|
||||
|
||||
+107
-79
@@ -14,104 +14,132 @@ def run_postprocessing(extras_mode,
|
||||
image_folder: list[tempfile.NamedTemporaryFile],
|
||||
input_dir,
|
||||
output_dir,
|
||||
extras_video,
|
||||
video,
|
||||
show_extras_results,
|
||||
*args,
|
||||
save_output: bool = True):
|
||||
devices.torch_gc()
|
||||
shared.state.begin('Extras')
|
||||
image_data = []
|
||||
image_names = []
|
||||
image_ext = []
|
||||
outputs = []
|
||||
params = {}
|
||||
info = ''
|
||||
if extras_mode == 1:
|
||||
for img in image_folder:
|
||||
if isinstance(img, Image.Image):
|
||||
image = img
|
||||
fn = ''
|
||||
ext = None
|
||||
else:
|
||||
shared.state.begin('Process')
|
||||
|
||||
def prepare_inputs(image):
|
||||
image_data = []
|
||||
image_names = []
|
||||
image_ext = []
|
||||
if extras_mode == 1: # process batch
|
||||
for img in image_folder:
|
||||
if isinstance(img, Image.Image):
|
||||
image = img
|
||||
fn = ''
|
||||
ext = None
|
||||
else:
|
||||
try:
|
||||
image = Image.open(os.path.abspath(img.name))
|
||||
except Exception as e:
|
||||
log.error(f'Failed to open image: file="{img.name}" {e}')
|
||||
continue
|
||||
fn, ext = os.path.splitext(img.orig_name)
|
||||
image_data.append(image)
|
||||
image_names.append(fn)
|
||||
image_ext.append(ext)
|
||||
log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}')
|
||||
elif extras_mode == 2: # process folder
|
||||
assert input_dir, 'input directory not selected'
|
||||
image_list = os.listdir(input_dir)
|
||||
for filename in image_list:
|
||||
fn = os.path.join(input_dir, filename)
|
||||
try:
|
||||
image = Image.open(os.path.abspath(img.name))
|
||||
image = Image.open(fn)
|
||||
except Exception as e:
|
||||
log.error(f'Failed to open image: file="{img.name}" {e}')
|
||||
log.error(f'Failed to open image: file="{fn}" {e}')
|
||||
continue
|
||||
fn, ext = os.path.splitext(img.orig_name)
|
||||
image_data.append(image)
|
||||
image_names.append(fn)
|
||||
image_ext.append(None)
|
||||
log.debug(f'Process: mode=folder inputs={input_dir} files={len(image_list)} images={len(image_data)}')
|
||||
elif extras_mode == 3: # process video
|
||||
pass
|
||||
else: # process image
|
||||
image_data.append(image)
|
||||
image_names.append(fn)
|
||||
image_ext.append(ext)
|
||||
log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}')
|
||||
elif extras_mode == 2:
|
||||
assert input_dir, 'input directory not selected'
|
||||
image_list = os.listdir(input_dir)
|
||||
for filename in image_list:
|
||||
fn = os.path.join(input_dir, filename)
|
||||
try:
|
||||
image = Image.open(fn)
|
||||
except Exception as e:
|
||||
log.error(f'Failed to open image: file="{fn}" {e}')
|
||||
continue
|
||||
image_data.append(image)
|
||||
image_names.append(fn)
|
||||
image_names.append(None)
|
||||
image_ext.append(None)
|
||||
log.debug(f'Process: mode=folder inputs={input_dir} files={len(image_list)} images={len(image_data)}')
|
||||
elif extras_mode == 3:
|
||||
log.error(f'Process: mode=video file="{extras_video}" not implemented yet')
|
||||
else:
|
||||
image_data.append(image)
|
||||
image_names.append(None)
|
||||
image_ext.append(None)
|
||||
return image_data, image_names, image_ext
|
||||
|
||||
image_data, image_names, image_ext = prepare_inputs(image)
|
||||
|
||||
if extras_mode == 2 and output_dir != '':
|
||||
outpath = output_dir
|
||||
else:
|
||||
outpath = resolve_output_path(opts.outdir_samples, opts.outdir_extras_samples)
|
||||
|
||||
processed_images = []
|
||||
for image, name, ext in zip(image_data, image_names, image_ext, strict=False): # pylint: disable=redefined-argument-from-local
|
||||
log.debug(f'Process: image={image} {args}')
|
||||
def process_images():
|
||||
outputs = []
|
||||
params = {}
|
||||
info = ''
|
||||
if shared.state.interrupted:
|
||||
log.debug('Postprocess interrupted')
|
||||
break
|
||||
if isinstance(image, str):
|
||||
try:
|
||||
image = Image.open(image)
|
||||
except Exception as e:
|
||||
log.error(f'Failed to open image: file="{image}" {e}')
|
||||
processed_images = []
|
||||
for image, name, ext in zip(image_data, image_names, image_ext, strict=False): # pylint: disable=redefined-argument-from-local
|
||||
log.debug(f'Process: image={image} {args}')
|
||||
info = ''
|
||||
if shared.state.interrupted:
|
||||
log.debug('Postprocess interrupted')
|
||||
break
|
||||
if isinstance(image, str):
|
||||
try:
|
||||
image = Image.open(image)
|
||||
except Exception as e:
|
||||
log.error(f'Failed to open image: file="{image}" {e}')
|
||||
continue
|
||||
if image is None:
|
||||
continue
|
||||
if image is None:
|
||||
continue
|
||||
shared.state.textinfo = name
|
||||
pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB"))
|
||||
shared.state.textinfo = name
|
||||
pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB"))
|
||||
scripts_manager.scripts_postproc.run(pp, args)
|
||||
geninfo, items = images.read_info_from_image(image)
|
||||
params = infotext.parse(geninfo)
|
||||
for k, v in items.items():
|
||||
pp.image.info[k] = v
|
||||
if 'parameters' in items:
|
||||
info = items['parameters'] + ', '
|
||||
if (params.get('size-1', 0) != pp.image.width) or (params.get('size-2', 0) != pp.image.height):
|
||||
params['size-1'] = pp.image.width
|
||||
params['size-2'] = pp.image.height
|
||||
info += f"Size: {pp.image.width}x{pp.image.height}, "
|
||||
info = info + ", ".join([k if k == v else f'{k}: {infotext.quote(v)}' for k, v in pp.info.items() if v is not None])
|
||||
pp.image.info["postprocessing"] = info
|
||||
processed_images.append(pp.image)
|
||||
if save_output:
|
||||
if opts.use_original_name_batch and name is not None:
|
||||
forced_filename = os.path.splitext(os.path.basename(name))[0]
|
||||
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=forced_filename)
|
||||
else:
|
||||
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info)
|
||||
if extras_mode != 2 or show_extras_results:
|
||||
outputs.append(pp.image)
|
||||
image.close()
|
||||
scripts_manager.scripts_postproc.postprocess(processed_images, args)
|
||||
return outputs, info, params
|
||||
|
||||
def process_video():
|
||||
outputs = []
|
||||
params = {}
|
||||
info = '' # TODO process: video add infotext
|
||||
if not video or not isinstance(video, str) or not os.path.isfile(video):
|
||||
log.error(f'Process: mode=video file="{video}" not found')
|
||||
return outputs, video, info, params
|
||||
log.debug(f'Process: video={video} {args}')
|
||||
shared.state.textinfo = video
|
||||
pp = scripts_postprocessing.PostprocessedImage(video=video)
|
||||
scripts_manager.scripts_postproc.run(pp, args)
|
||||
geninfo, items = images.read_info_from_image(image)
|
||||
params = infotext.parse(geninfo)
|
||||
for k, v in items.items():
|
||||
pp.image.info[k] = v
|
||||
if 'parameters' in items:
|
||||
info = items['parameters'] + ', '
|
||||
if (params.get('size-1', 0) != pp.image.width) or (params.get('size-2', 0) != pp.image.height):
|
||||
params['size-1'] = pp.image.width
|
||||
params['size-2'] = pp.image.height
|
||||
info += f"Size: {pp.image.width}x{pp.image.height}, "
|
||||
info = info + ", ".join([k if k == v else f'{k}: {infotext.quote(v)}' for k, v in pp.info.items() if v is not None])
|
||||
pp.image.info["postprocessing"] = info
|
||||
processed_images.append(pp.image)
|
||||
if save_output:
|
||||
if opts.use_original_name_batch and name is not None:
|
||||
forced_filename = os.path.splitext(os.path.basename(name))[0]
|
||||
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=forced_filename)
|
||||
else:
|
||||
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info)
|
||||
if extras_mode != 2 or show_extras_results:
|
||||
outputs.append(pp.image)
|
||||
image.close()
|
||||
scripts_manager.scripts_postproc.postprocess(processed_images, args)
|
||||
return pp.video, info, params
|
||||
|
||||
if extras_mode == 3:
|
||||
video, info, params = process_video()
|
||||
outputs = []
|
||||
else:
|
||||
outputs, info, params = process_images()
|
||||
video = None
|
||||
|
||||
devices.torch_gc()
|
||||
return outputs, info, params
|
||||
return outputs, video, info, params
|
||||
|
||||
|
||||
def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, video, show_extras_results, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, save_output: bool = True, script_args: dict | None = None):
|
||||
|
||||
@@ -5,14 +5,15 @@ from modules.logger import log
|
||||
|
||||
|
||||
class PostprocessedImage:
|
||||
def __init__(self, image, info = None):
|
||||
def __init__(self, image = None, video = None, info = None):
|
||||
if info is None:
|
||||
info = {}
|
||||
self.image = image
|
||||
self.video = video
|
||||
self.info = info
|
||||
|
||||
def __str__(self):
|
||||
return f'PostprocessedImage(image={self.image} info={self.info})'
|
||||
return f'PostprocessedImage(image={self.image} video={self.video} info={self.info})'
|
||||
|
||||
|
||||
class ScriptPostprocessing:
|
||||
@@ -160,6 +161,6 @@ class ScriptPostprocessingRunner:
|
||||
else:
|
||||
for (name, _component), value in zip(script.controls.items(), script_args, strict=False):
|
||||
process_kwargs[name] = value
|
||||
log.debug(f'Postprocess: script={script.name} args={process_args} kwargs={process_kwargs}')
|
||||
log.debug(f'Postprocess: script="{script.name}" args={process_args} kwargs={process_kwargs}')
|
||||
script.postprocess(filenames, *process_args, **process_kwargs)
|
||||
shared.state.end(jobid)
|
||||
|
||||
@@ -32,8 +32,10 @@ def submit_video(video):
|
||||
|
||||
def submit_process(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, save_output, *script_inputs):
|
||||
from modules.ui_common import infotext_to_html
|
||||
result_images, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, *script_inputs, save_output=save_output)
|
||||
return result_images, geninfo, infotext_to_html(geninfo)
|
||||
result_images, result_video, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, *script_inputs, save_output=save_output)
|
||||
gr_result_image = gr.update(value=result_images, visible=tab_index != 3)
|
||||
gr_result_video = gr.update(value=result_video, visible=tab_index == 3)
|
||||
return gr_result_image, gr_result_video, geninfo, infotext_to_html(geninfo)
|
||||
|
||||
|
||||
def create_ui():
|
||||
@@ -51,12 +53,12 @@ def create_ui():
|
||||
extras_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Leave blank to save images to the default path.", elem_id="extras_batch_output_dir")
|
||||
show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results")
|
||||
with gr.Tab('Process Video', id="process_video", elem_id="extras_process_video_tab") as tab_process_video:
|
||||
extras_video = gr.Video(label="Input Video", show_label=False, interactive=True, elem_id="extras_video")
|
||||
extras_video = gr.Video(label="Input Video", show_label=False, height=512, interactive=True, elem_id="extras_video")
|
||||
with gr.Row():
|
||||
save_output = gr.Checkbox(label='Save output', value=True, elem_id="extras_save_output")
|
||||
|
||||
script_inputs = scripts_manager.scripts_postproc.setup_ui()
|
||||
with gr.Column():
|
||||
with gr.Column(elem_id="extras_output_column"):
|
||||
id_part = 'extras'
|
||||
with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"):
|
||||
submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
|
||||
@@ -66,9 +68,16 @@ def create_ui():
|
||||
skip.click(fn=shared.state.skip, inputs=[], outputs=[])
|
||||
pause = gr.Button('Pause', elem_id=f"{id_part}_pause")
|
||||
pause.click(fn=shared.state.pause, _js='checkPaused', inputs=[], outputs=[])
|
||||
result_images, generation_info, _html_info, html_info_formatted, _html_log = ui_common.create_output_panel("extras")
|
||||
|
||||
with gr.Tabs(elem_id="extras_output_tabs"):
|
||||
with gr.Tab('Image', id="process_output_image", elem_id="extras_output_image_tab"):
|
||||
result_images, generation_info, _html_info, html_info_formatted, _html_log = ui_common.create_output_panel("extras")
|
||||
with gr.Tab('Video', id="process_output_video", elem_id="extras_output_video_tab"):
|
||||
result_video = gr.Video(label="Video", show_label=False, interactive=False, elem_id="extras_output_video", visible=False)
|
||||
|
||||
gr.HTML('File metadata')
|
||||
exif_info = gr.HTML(elem_id="pnginfo_html_info")
|
||||
|
||||
with gr.Row(elem_id='copy_buttons_process'):
|
||||
copy_process_buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "control", "caption"])
|
||||
|
||||
@@ -100,6 +109,7 @@ def create_ui():
|
||||
],
|
||||
outputs=[
|
||||
result_images,
|
||||
result_video,
|
||||
generation_info,
|
||||
html_info_formatted,
|
||||
]
|
||||
|
||||
@@ -256,6 +256,7 @@ def save_video(
|
||||
stream=None, # async progress reporting stream
|
||||
metadata: dict | None = None, # metadata for video
|
||||
pbar=None, # progress bar for video
|
||||
reclamp: bool = True, # reclamp pixels to [-1, 1] range
|
||||
):
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
@@ -288,6 +289,8 @@ def save_video(
|
||||
log.error(f'Video: type={type(pixels)} not a tensor')
|
||||
return 0, output_video, None
|
||||
t_save = time.time()
|
||||
if pixels.ndim == 4:
|
||||
pixels = pixels.unsqueeze(0)
|
||||
n, _c, t, h, w = pixels.shape
|
||||
size = pixels.element_size() * pixels.numel()
|
||||
log.debug(f'Video: video={mp4_video} export={mp4_frames} safetensors={mp4_sf} interpolate={mp4_interpolate}')
|
||||
@@ -304,8 +307,10 @@ def save_video(
|
||||
pixels = pixels.permute(1, 2, 0, 3, 4)
|
||||
pixels = pixels * 2.0 - 1.0
|
||||
|
||||
n, _c, t, h, w = pixels.shape
|
||||
x = torch.clamp(pixels.float(), -1., 1.) * 127.5 + 127.5
|
||||
if reclamp:
|
||||
x = torch.clamp(pixels.float(), -1., 1.) * 127.5 + 127.5
|
||||
else:
|
||||
x = pixels.float() * 255.0
|
||||
x = x.detach().cpu().to(torch.uint8)
|
||||
x = einops.rearrange(x, '(m n) c t h w -> t (m h) (n w) c', n=n)
|
||||
x = x.contiguous()
|
||||
|
||||
@@ -16,6 +16,8 @@ class ScriptPostprocessingColorGrading(scripts_postprocessing.ScriptPostprocessi
|
||||
grading_params = processing_grading.GradingParams(*args, **kwargs)
|
||||
if not processing_grading.is_active(grading_params):
|
||||
return
|
||||
if pp.image is None:
|
||||
return
|
||||
pp.image = processing_grading.grade_image(pp.image, grading_params)
|
||||
defaults = processing_grading.GradingParams()
|
||||
for f in fields(grading_params):
|
||||
|
||||
@@ -28,6 +28,10 @@ class ScriptPostprocessingDetailer(scripts_postprocessing.ScriptPostprocessing):
|
||||
sampler='Default', prediction='default', shift=3.0, cfg_scale=6.0, options=None, seed=-1):
|
||||
if not enabled:
|
||||
return pp
|
||||
if not shared.sd_loaded:
|
||||
log.warning('Detailer postprocess: SD model not loaded')
|
||||
pp.info["Detailer"] = "skipped (SD model not loaded)"
|
||||
return pp
|
||||
if shared.sd_model is None or not hasattr(shared.sd_model, 'sd_checkpoint_info'):
|
||||
log.warning('Detailer postprocess: no base model selected')
|
||||
pp.info["Detailer"] = "skipped (no base model selected)"
|
||||
|
||||
@@ -31,6 +31,8 @@ class ScriptPixelArt(scripts_postprocessing.ScriptPostprocessing):
|
||||
return
|
||||
from modules.postprocess.pixelart import img_to_pixelart, edge_detect_for_pixelart
|
||||
pixel_image = pp.image
|
||||
if pixel_image is None:
|
||||
return
|
||||
|
||||
if pixelart_use_edge_detection:
|
||||
pixel_image = edge_detect_for_pixelart(pixel_image, image_weight=pixelart_image_weight, block_size=pixelart_edge_block_size, device=devices.device)
|
||||
|
||||
@@ -69,7 +69,8 @@ class ScriptPostprocessingRembg(scripts_postprocessing.ScriptPostprocessing):
|
||||
else:
|
||||
image = pp.image
|
||||
info = pp.info
|
||||
|
||||
if image is None:
|
||||
return pp
|
||||
log.info(f'RemoveBackground: model={model} merge_alpha={merge_alpha} refine={refine} mask_only={mask_only} postprocess_mask={postprocess_mask} alpha_matting={alpha_matting} alpha_matting_foreground_threshold={alpha_matting_foreground_threshold} alpha_matting_background_threshold={alpha_matting_background_threshold} alpha_matting_erode_size={alpha_matting_erode_size}')
|
||||
if model == 'ben2':
|
||||
try:
|
||||
|
||||
@@ -13,18 +13,23 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
|
||||
with gr.Accordion(self.name, open = False, elem_id="postprocess_seedvr_accordion"):
|
||||
with gr.Row():
|
||||
seedvr_enabled = gr.Checkbox(label="Enable SeedVR", value=False, elem_id="extras_seedvr_enabled")
|
||||
seedvr_offload = gr.Checkbox(label="Offload model", value=True, elem_id="extras_seedvr_offload")
|
||||
with gr.Row():
|
||||
seedvr_selected = gr.Dropdown(label="SeedVR model", choices=list(MODELS_MAP.keys()), value=list(MODELS_MAP.keys())[0], elem_id="extras_seedvr_model")
|
||||
with gr.Row():
|
||||
seedvr_scale = gr.Slider(minimum=1, maximum=16, step=0.1, value=2, label="SeedVR scale", elem_id="extras_seedvr_scale")
|
||||
seedvr_steps = gr.Slider(step=1, value=1, minimum=1, maximum=99, label="SeedVR steps", elem_id="extras_seedvr_steps")
|
||||
with gr.Row():
|
||||
seedvr_seed = gr.Number(step=1, value=-1, label="SeedVR seed", elem_id="extras_seedvr_seed")
|
||||
seedvr_steps = gr.Number(step=1, value=1, minimum=1, maximum=99, label="SeedVR steps", elem_id="extras_seedvr_steps", visible=False)
|
||||
with gr.Row():
|
||||
seedvr_cfg_scale = gr.Slider(minimum=0.0, maximum=15.0, step=0.01, value=1.5, label="SeedVR guidance scale", elem_id="extras_seedvr_cfg_scale")
|
||||
seedvr_cfg_rescale = gr.Slider(minimum=0.0, maximum=15.0, step=0.01, value=0.0, label="SeedVR guidance rescale", elem_id="extras_seedvr_cfg_rescale")
|
||||
with gr.Row():
|
||||
seedvr_tile_size = gr.Slider(minimum=64, maximum=4096, step=8, value=1024, label="SeedVR tile size", elem_id="extras_seedvr_tile_size")
|
||||
seedvr_tile_overlap = gr.Slider(minimum=0, maximum=1.0, step=0.01, value=0.25, label="SeedVR tile overlap", elem_id="extras_seedvr_tile_overlap")
|
||||
with gr.Row():
|
||||
seedvr_batch_size = gr.Slider(minimum=1, maximum=64, step=1, value=1, label="SeedVR batch size", elem_id="extras_seedvr_batch_size")
|
||||
seedvr_batch_overlap = gr.Slider(minimum=0, maximum=16, step=1, value=0, label="SeedVR batch overlap", elem_id="extras_seedvr_batch_overlap")
|
||||
return {
|
||||
"seedvr_enabled": seedvr_enabled,
|
||||
"seedvr_selected": seedvr_selected,
|
||||
@@ -35,6 +40,9 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
|
||||
"seedvr_cfg_rescale": seedvr_cfg_rescale,
|
||||
"seedvr_tile_size": seedvr_tile_size,
|
||||
"seedvr_tile_overlap": seedvr_tile_overlap,
|
||||
"seedvr_batch_size": seedvr_batch_size,
|
||||
"seedvr_batch_overlap": seedvr_batch_overlap,
|
||||
"seedvr_offload": seedvr_offload,
|
||||
}
|
||||
|
||||
def process(self,
|
||||
@@ -47,22 +55,27 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
|
||||
seedvr_cfg_scale: float,
|
||||
seedvr_cfg_rescale: float,
|
||||
seedvr_tile_size: int,
|
||||
seedvr_tile_overlap: float
|
||||
seedvr_tile_overlap: float,
|
||||
seedvr_batch_size: int,
|
||||
seedvr_batch_overlap: int,
|
||||
seedvr_offload: bool
|
||||
): # pylint: disable=arguments-differ
|
||||
if not seedvr_enabled:
|
||||
return
|
||||
from modules import shared, upscaler
|
||||
from modules.logger import log
|
||||
image = pp.image
|
||||
_input = pp.image or pp.video
|
||||
if _input is None:
|
||||
return
|
||||
instance: upscaler.UpscalerData = next(iter([x for x in shared.sd_upscalers if x.name == seedvr_selected]), None)
|
||||
scaler: UpscalerSeedVR = instance.scaler
|
||||
|
||||
log.info(f'Upscaler: type="SeedVR" model="{seedvr_selected}" scale={seedvr_scale} seed={seedvr_seed} steps={seedvr_steps} cfg_scale={seedvr_cfg_scale} cfg_rescale={seedvr_cfg_rescale} tile_size={seedvr_tile_size} tile_overlap={seedvr_tile_overlap}')
|
||||
log.info(f'Upscaler: type="SeedVR" model="{seedvr_selected}" scale={seedvr_scale} seed={seedvr_seed} steps={seedvr_steps} cfg_scale={seedvr_cfg_scale} cfg_rescale={seedvr_cfg_rescale} tile_size={seedvr_tile_size} tile_overlap={seedvr_tile_overlap} batch_size={seedvr_batch_size} batch_overlap={seedvr_batch_overlap}')
|
||||
|
||||
jobid = shared.state.begin('Upscale')
|
||||
|
||||
scaler.scale = float(seedvr_scale)
|
||||
upscaled = scaler.do_upscale(image,
|
||||
upscaled = scaler.do_upscale(_input,
|
||||
seedvr_selected,
|
||||
cfg_scale=seedvr_cfg_scale,
|
||||
cfg_rescale=seedvr_cfg_rescale,
|
||||
@@ -70,8 +83,14 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
|
||||
seed=seedvr_seed,
|
||||
tile_size=seedvr_tile_size,
|
||||
tile_overlap=seedvr_tile_overlap,
|
||||
batch_size=seedvr_batch_size,
|
||||
batch_overlap=seedvr_batch_overlap,
|
||||
offload=seedvr_offload
|
||||
)
|
||||
shared.state.end(jobid)
|
||||
|
||||
pp.image = upscaled
|
||||
if isinstance(upscaled, str):
|
||||
pp.video = upscaled
|
||||
else:
|
||||
pp.image = upscaled
|
||||
pp.info["SeedVR"] = f"Scale={seedvr_scale} Seed={seedvr_seed} CFG Scale={seedvr_cfg_scale} CFG Rescale={seedvr_cfg_rescale}"
|
||||
|
||||
@@ -52,6 +52,8 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
|
||||
}
|
||||
|
||||
def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop):
|
||||
if image is None:
|
||||
return None
|
||||
if upscale_mode == 1:
|
||||
upscale_by = max(upscale_to_width / image.width, upscale_to_height / image.height)
|
||||
info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}"
|
||||
|
||||
Vendored
+1
-1
@@ -11713,7 +11713,7 @@ function updateImg2imgResizeToTextAfterChangingImage() {
|
||||
}
|
||||
async function toggleCompact(val, old) {
|
||||
if (val === old) return;
|
||||
log("toggleCompact", val, old);
|
||||
log("toggleCompact", val);
|
||||
if (val) {
|
||||
gradioApp().style.setProperty("--layout-gap", "var(--spacing-md)");
|
||||
gradioApp().querySelectorAll("input[type=range]").forEach((el2) => el2.classList.add("hidden"));
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -729,7 +729,7 @@ function createThemeElement(): HTMLImageElement {
|
||||
|
||||
export async function toggleCompact(val, old) {
|
||||
if (val === old) return;
|
||||
log('toggleCompact', val, old);
|
||||
log('toggleCompact', val);
|
||||
if (val) {
|
||||
gradioApp().style.setProperty('--layout-gap', 'var(--spacing-md)');
|
||||
gradioApp().querySelectorAll('input[type=range]').forEach((el) => el.classList.add('hidden'));
|
||||
|
||||
Reference in New Issue
Block a user