diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc9c5b8b..4edfa26a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,14 +35,31 @@ Service release addressing all zero-day issues reported so far... ### Dev branch -- remove external clone of items in `/repositories` -- add **lora oft** support, thanks @antis0007 and @ai-casanova -- **upscalers** - - **compile compile** option, thanks @disty0 - - **chainner** add high quality models from [Helaman](https://openmodeldb.info/users/helaman) - - **chainner** switch to `torchvision.transforms` for all image decode operations -- new option: *settings -> images -> keep incomplete* - can be used to skip vae decode on aborted/skipped/interrupted image generations +*Note*: Pending release of `diffusers==0.22.0` + +- **Diffusers** + - new model type: [SegMind SSD-1B](https://huggingface.co/segmind/SSD-1B) + its a distilled model, this time 50% smaller and faster version of SD-XL! + test shows batch-size:4 with 1k images used less than 6.5GB of VRAM + download using built-in **Huggingface** downloader: `segmind/SSD-1B` + - new model type: [OpenAI Consistency Models](https://github.com/openai/consistency_models) + near-instant generate in a one or two steps! + current list of models is very limited as they are not general purpose models, but that is expected to change + download using built-in **Huggingface** downloaded: `openai/diffusers` + - add support for **Custom pipelines**, thanks @disty0 + custom pipelines can be downloaded using built-in **Huggingface** downloaded + think of them as plugins for diffusers not unlike original extensions that modify behavior of `ldm` backend + list of community pipelines: + +- **General** + - add **Lora OFT** support, thanks @antis0007 and @ai-casanova + - **Upscalers** + - **compile compile** option, thanks @disty0 + - **chaiNNer** add high quality models from [Helaman](https://openmodeldb.info/users/helaman) + - redesigned **progress bar** with full details on current operation + - new option: *settings -> images -> keep incomplete* + can be used to skip vae decode on aborted/skipped/interrupted image generations + - remove external clone of items in `/repositories` **Themes** diff --git a/extensions-builtin/sd-extension-chainner b/extensions-builtin/sd-extension-chainner index 9d5e6c222..2488b2357 160000 --- a/extensions-builtin/sd-extension-chainner +++ b/extensions-builtin/sd-extension-chainner @@ -1 +1 @@ -Subproject commit 9d5e6c22232f8cb850e3603a9ba94781304a912d +Subproject commit 2488b23571e86da6fc180c072870145f3b4fb32c diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 2e9cdd463..99b2cafbc 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 2e9cdd46358bba6ccad421274dd9375ec9f2d0c6 +Subproject commit 99b2cafbc2b4a2fc93ffcabd56b0ff915396d1f1 diff --git a/javascript/base.css b/javascript/base.css index a1cc4878e..cb28d8412 100644 --- a/javascript/base.css +++ b/javascript/base.css @@ -14,7 +14,6 @@ width: 22em; min-height: 1.3em; font-size: 0.8em; 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; } -.jobStatus { position: fixed; bottom: 1em; right: 1em; background: var(--input-background-fill); padding: 0.4em; font-size: 0.8em; color: var(--body-text-color-subdued); } /* live preview */ .progressDiv{ position: relative; height: 20px; background: #b4c0cc; margin-bottom: -3px; } diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js index ca372b1f1..bca665672 100644 --- a/javascript/logMonitor.js +++ b/javascript/logMonitor.js @@ -1,6 +1,5 @@ let logMonitorEl = null; let logMonitorStatus = true; -let jobStatusEl = null; async function logMonitor() { if (logMonitorStatus) setTimeout(logMonitor, opts.logmonitor_refresh_period); @@ -52,10 +51,6 @@ async function initLogMonitor() { `; el.style.display = 'none'; - jobStatusEl = document.createElement('div'); - jobStatusEl.className = 'jobStatus'; - jobStatusEl.style.display = 'none'; - gradioApp().appendChild(jobStatusEl); fetch(`/sdapi/v1/start?agent=${encodeURI(navigator.userAgent)}`); logMonitor(); log('initLogMonitor'); diff --git a/javascript/progressBar.js b/javascript/progressBar.js index 3da707627..55f5ac1ea 100644 --- a/javascript/progressBar.js +++ b/javascript/progressBar.js @@ -42,24 +42,24 @@ function checkPaused(state) { function setProgress(res) { const elements = ['txt2img_generate', 'img2img_generate', 'extras_generate']; const progress = (res?.progress || 0); + const job = res?.job || ''; const perc = res && (progress > 0) ? `${Math.round(100.0 * progress)}%` : ''; let sec = res?.eta || 0; let eta = ''; + console.log('HERE', res); if (res?.paused) eta = 'Paused'; else if (res?.completed || (progress > 0.99)) eta = 'Finishing'; - else if (sec === 0) eta = `Init${res?.job?.length > 0 ? `: ${res.job}` : ''}`; + else if (sec === 0) eta = 'Starting'; else { const min = Math.floor(sec / 60); sec %= 60; - eta = min > 0 ? `ETA: ${Math.round(min)}m ${Math.round(sec)}s` : `ETA: ${Math.round(sec)}s`; + eta = min > 0 ? `${Math.round(min)}m ${Math.round(sec)}s` : `${Math.round(sec)}s`; } document.title = `SD.Next ${perc}`; for (const elId of elements) { const el = document.getElementById(elId); - el.innerText = res - ? `${perc} ${eta}` - : 'Generate'; - el.style.background = res + el.innerText = (res ? `${job} ${perc} ${eta}` : 'Generate'); + el.style.background = res && (progress > 0) ? `linear-gradient(to right, var(--primary-500) 0%, var(--primary-800) ${perc}, var(--neutral-700) ${perc})` : 'var(--button-primary-background-fill)'; } @@ -106,7 +106,6 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres debug('taskEnd:', id_task); localStorage.removeItem('task'); setProgress(); - if (jobStatusEl) jobStatusEl.style.display = 'none'; if (parentGallery && livePreview) parentGallery.removeChild(livePreview); checkPaused(true); if (atEnd) atEnd(); @@ -114,8 +113,6 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres const start = (id_task, id_live_preview) => { // eslint-disable-line no-shadow request('./internal/progress', { id_task, id_live_preview }, (res) => { - if (jobStatusEl) jobStatusEl.innerText = (res?.job || '').trim().toUpperCase(); - if (jobStatusEl) jobStatusEl.style.display = jobStatusEl.innerText.length > 0 ? 'block' : 'none'; lastState = res; const elapsedFromStart = (new Date() - dateStart) / 1000; hasStarted |= res.active; diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 1c3313ade..f8344cbaf 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -72,7 +72,7 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- #txt2img_footer, #img2img_footer { height: fit-content; display: none; } #txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: wrap-reverse; height: fit-content; } #txt2img_actions_column, #img2img_actions_column { gap: 0.5em; height: fit-content; } -#txt2img_generate_box > button, #img2img_generate_box > button { min-height: 42px; max-height: 42px; } +#txt2img_generate_box > button, #img2img_generate_box > button, #txt2img_enqueue, #img2img_enqueue { min-height: 42px; max-height: 42px; font-size: var(--input-text-size); line-height: 1em; } #txt2img_generate_line2, #img2img_generate_line2, #txt2img_tools, #img2img_tools { display: flex; } #txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button, #txt2img_tools > button, #img2img_tools > button { height: 2em; line-height: 0; font-size: var(--input-text-size); min-width: unset; display: block !important; margin-left: 0.4em; margin-right: 0.4em; } @@ -96,7 +96,6 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } width: 22em; min-height: 1.3em; font-size: 0.8em; 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; } -.jobStatus { position: fixed; bottom: 1em; right: 1em; background: var(--input-background-fill); padding: 0.4em; font-size: 0.8em; color: var(--body-text-color-subdued); } /* settings */ #si-sparkline-memo, #si-sparkline-load { background-color: #111; } diff --git a/modules/api/api.py b/modules/api/api.py index 6cdb362d5..29689ab85 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -356,68 +356,54 @@ class Api: def extras_batch_images_api(self, req: models.ExtrasBatchImagesRequest): reqDict = setUpscalers(req) - image_list = reqDict.pop('imageList', []) image_folder = [decode_base64_to_image(x.data) for x in image_list] - with self.queue_lock: result = postprocessing.run_extras(extras_mode=1, image_folder=image_folder, image="", input_dir="", output_dir="", save_output=False, **reqDict) - return models.ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) def pnginfoapi(self, req: models.PNGInfoRequest): if not req.image.strip(): return models.PNGInfoResponse(info="") - image = decode_base64_to_image(req.image.strip()) if image is None: return models.PNGInfoResponse(info="") - geninfo, items = images.read_info_from_image(image) if geninfo is None: geninfo = "" - items = {**{'parameters': geninfo}, **items} - return models.PNGInfoResponse(info=geninfo, items=items) def progressapi(self, req: models.ProgressRequest = Depends()): - # copy from check_progress_call of ui.py - if shared.state.job_count == 0: return models.ProgressResponse(progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo) - # avoid dividing zero - progress = 0.01 - - if shared.state.job_count > 0: - progress += shared.state.job_no / shared.state.job_count - if shared.state.sampling_steps > 0: - progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps - - time_since_start = time.time() - shared.state.time_start - eta = time_since_start / progress - eta_relative = eta-time_since_start - - progress = min(progress, 1) - shared.state.set_current_image() - current_image = None if shared.state.current_image and not req.skip_current_image: current_image = encode_pil_to_base64(shared.state.current_image) - return models.ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) + batch_x = max(shared.state.job_no, 0) + batch_y = max(shared.state.job_count, 1) + step_x = max(shared.state.sampling_step, 0) + step_y = max(shared.state.sampling_steps, 1) + current = step_y * batch_x + step_x + total = step_y * batch_y + progress = current / total if total > 0 else 0 + + time_since_start = time.time() - shared.state.time_start + eta_relative = (time_since_start / progress) - time_since_start + + res = models.ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) + return res + def interrogateapi(self, interrogatereq: models.InterrogateRequest): image_b64 = interrogatereq.image if image_b64 is None: raise HTTPException(status_code=404, detail="Image not found") - img = decode_base64_to_image(image_b64) img = img.convert('RGB') - - # Override object param with self.queue_lock: if interrogatereq.model == "clip": processed = shared.interrogator.interrogate(img) @@ -425,7 +411,6 @@ class Api: processed = deepbooru.model.tag(img) else: raise HTTPException(status_code=404, detail="Model not found") - return models.InterrogateResponse(caption=processed) def interruptapi(self): @@ -473,18 +458,8 @@ class Api: def get_sd_vaes(self): return [{"model_name": x, "filename": vae_dict[x]} for x in vae_dict.keys()] - def get_upscalers(self): - return [ - { - "name": upscaler.name, - "model_name": upscaler.scaler.model_name, - "model_path": upscaler.data_path, - "model_url": None, - "scale": upscaler.scale, - } - for upscaler in shared.sd_upscalers - ] + return [{"name": upscaler.name, "model_name": upscaler.scaler.model_name, "model_path": upscaler.data_path, "model_url": None, "scale": upscaler.scale} for upscaler in shared.sd_upscalers] def get_sd_models(self): return [{"title": x.title, "name": x.name, "filename": x.filename, "type": x.type, "hash": x.shorthash, "sha256": x.sha256, "config": find_checkpoint_config_near_filename(x)} for x in checkpoints_list.values()] @@ -500,23 +475,13 @@ class Api: def get_embeddings(self): db = sd_hijack.model_hijack.embedding_db - def convert_embedding(embedding): - return { - "step": embedding.step, - "sd_checkpoint": embedding.sd_checkpoint, - "sd_checkpoint_name": embedding.sd_checkpoint_name, - "shape": embedding.shape, - "vectors": embedding.vectors, - } + return {"step": embedding.step, "sd_checkpoint": embedding.sd_checkpoint, "sd_checkpoint_name": embedding.sd_checkpoint_name, "shape": embedding.shape, "vectors": embedding.vectors} def convert_embeddings(embeddings): return {embedding.name: convert_embedding(embedding) for embedding in embeddings.values()} - return { - "loaded": convert_embeddings(db.word_embeddings), - "skipped": convert_embeddings(db.skipped_embeddings), - } + return {"loaded": convert_embeddings(db.word_embeddings), "skipped": convert_embeddings(db.skipped_embeddings)} def get_extra_networks(self, page: Optional[str] = None, name: Optional[str] = None, filename: Optional[str] = None, title: Optional[str] = None, fullname: Optional[str] = None, hash: Optional[str] = None): # pylint: disable=redefined-builtin res = [] @@ -553,7 +518,7 @@ class Api: def create_embedding(self, args: dict): try: - shared.state.begin('api-create-embedding') + shared.state.begin('api-embedding') filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used shared.state.end() @@ -564,7 +529,7 @@ class Api: def create_hypernetwork(self, args: dict): try: - shared.state.begin('api-create-hypernetwork') + shared.state.begin('api-hypernetwork') filename = create_hypernetwork(**args) # create empty embedding # pylint: disable=E1111 shared.state.end() return models.CreateResponse(info = f"create hypernetwork filename: {filename}") @@ -590,7 +555,7 @@ class Api: def train_embedding(self, args: dict): try: - shared.state.begin('api-train-embedding') + shared.state.begin('api-embedding') apply_optimizations = False error = None filename = '' @@ -611,7 +576,7 @@ class Api: def train_hypernetwork(self, args: dict): try: - shared.state.begin('api-train-hypernetwork') + shared.state.begin('api-hypernetwork') shared.loaded_hypernetworks = [] apply_optimizations = False error = None diff --git a/modules/extras.py b/modules/extras.py index 6e61b8426..a84b9b28e 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -54,7 +54,7 @@ def to_half(tensor, enable): def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae, discard_weights, save_metadata): # pylint: disable=unused-argument - shared.state.begin('model-merge') + shared.state.begin('merge') save_as_half = save_as_half == 0 def fail(message): @@ -319,7 +319,7 @@ def run_modelconvert(model, checkpoint_formats, precision, conv_type, custom_nam "vae": vae_conv, "other": others_conv } - shared.state.begin('model-convert') + shared.state.begin('convert') model_info = sd_models.checkpoints_list[model] shared.state.textinfo = f"Loading {model_info.filename}..." shared.log.info(f"Model convert loading: {model_info.filename}") diff --git a/modules/hashes.py b/modules/hashes.py index 21c43de17..e4d13f2e3 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -69,7 +69,7 @@ def sha256(filename, title, use_addnet_hash=False): if not os.path.isfile(filename): return None orig_state = copy.deepcopy(shared.state) - shared.state.begin("hashing") + shared.state.begin("hash") if use_addnet_hash: if progress_ok: try: diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index d36edb0d1..d3572cbd0 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -460,7 +460,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi hypernetwork.load(path) shared.loaded_hypernetworks = [hypernetwork] - shared.state.job = "train-hypernetwork" + shared.state.job = "train" shared.state.textinfo = "Initializing hypernetwork training..." shared.state.job_count = steps diff --git a/modules/img2img.py b/modules/img2img.py index f01031cb8..b05254434 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -40,7 +40,6 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) btcrept = p.batch_size shared.log.info(f"Process batch: inputs={len(image_files)} outputs={p.n_iter * p.batch_size} per input") for i in range(0, len(image_files), window_size): - shared.state.job = f"{i+1} to {min(i+window_size, len(image_files))} out of {len(image_files)}" if shared.state.skipped: shared.state.skipped = False if shared.state.interrupted: diff --git a/modules/modelloader.py b/modules/modelloader.py index dee78797d..0173720d3 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -85,7 +85,7 @@ def download_civit_preview(model_path: str, preview_url: str): block_size = 16384 # 16KB blocks written = 0 img = None - shared.state.begin('civitai-download-preview') + shared.state.begin('civitai') try: with open(preview_file, 'wb') as f: with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), console=shared.console) as progress: @@ -142,7 +142,7 @@ def download_civit_model_thread(model_name, model_url, model_path, model_type, p total_size = int(r.headers.get('content-length', 0)) res += f' size={round((starting_pos + total_size)/1024/1024)}Mb' shared.log.info(res) - shared.state.begin('civitai-download-model') + shared.state.begin('civitai') block_size = 16384 # 16KB blocks written = starting_pos global download_pbar # pylint: disable=global-statement @@ -188,7 +188,7 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config return None from diffusers import DiffusionPipeline import huggingface_hub as hf - shared.state.begin('huggingface-download-model') + shared.state.begin('huggingface') if download_config is None: download_config = { "force_download": False, diff --git a/modules/processing.py b/modules/processing.py index 616533987..70db97412 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -442,6 +442,8 @@ def decode_first_stage(model, x, full_quality=True): shared.log.debug(f'Decode VAE: skipped={shared.state.skipped} interrupted={shared.state.interrupted}') x_sample = torch.zeros((len(x), 3, x.shape[2] * 8, x.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device) return x_sample + prev_job = shared.state.job + shared.state.job = 'vae' with devices.autocast(disable = x.dtype==devices.dtype_vae): try: if full_quality: @@ -459,6 +461,7 @@ def decode_first_stage(model, x, full_quality=True): except Exception as e: x_sample = x shared.log.error(f'Decode VAE: {e}') + shared.state.job = prev_job return x_sample @@ -769,12 +772,11 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: return '' ema_scope_context = p.sd_model.ema_scope if shared.backend == shared.Backend.ORIGINAL else nullcontext + shared.state.job_count = p.n_iter with devices.inference_context(), ema_scope_context(): t0 = time.time() with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) - if shared.state.job_count == -1: - shared.state.job_count = p.n_iter extra_network_data = None for n in range(p.n_iter): p.iteration = n @@ -806,8 +808,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: step_multiplier = 1 sampler_config = modules.sd_samplers.find_sampler_config(p.sampler_name) step_multiplier = 2 if sampler_config and sampler_config.options.get("second_order", False) else 1 - if p.n_iter > 1: - shared.state.job = f"Batch {n+1} out of {p.n_iter}" if shared.backend == shared.Backend.ORIGINAL: uc = get_conds_with_caching(modules.prompt_parser.get_learned_conditioning, p.negative_prompts, p.steps * step_multiplier, cached_uc) @@ -913,7 +913,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: output_images.append(image_mask_composite) del x_samples_ddim devices.torch_gc() - shared.state.nextjob() t1 = time.time() shared.log.info(f'Processed: images={len(output_images)} time={t1 - t0:.2f}s its={(p.steps * len(output_images)) / (t1 - t0):.2f} memory={modules.memstats.memory_stats()}') @@ -1036,12 +1035,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.is_hr_pass = False return self.is_hr_pass = True - if not shared.state.processing_has_refined_job_count: - if shared.state.job_count == -1: - shared.state.job_count = self.n_iter - shared.state.job_count = shared.state.job_count * 2 - shared.state.processing_has_refined_job_count = True hypertile_set(self, hr=True) + shared.state.job_count = 2 * self.n_iter shared.log.debug(f'Init hires: upscaler="{self.hr_upscaler}" sampler="{self.latent_sampler}" resize={self.hr_resize_x}x{self.hr_resize_y} upscale={self.hr_upscale_to_x}x{self.hr_upscale_to_y}') def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): @@ -1061,11 +1056,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.sampler.initialize(self) x = create_random_tensors([4, self.height // 8, self.width // 8], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x)) + shared.state.nextjob() if not self.enable_hr or shared.state.interrupted or shared.state.skipped: return samples self.init_hr() if self.is_hr_pass: + prev_job = shared.state.job target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y decoded_samples = None @@ -1083,6 +1080,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.extra_generation_params, self.restore_faces = bak_extra_generation_params, bak_restore_faces images.save_image(image, self.outpath_samples, "", seeds[i], prompts[i], shared.opts.samples_format, info=info, suffix="-before-hires") if latent_scale_mode is None or self.hr_force: # non-latent upscaling + shared.state.job = 'upscale' if decoded_samples is None: decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae), self.full_quality) decoded_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) @@ -1112,6 +1110,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if self.latent_sampler == "PLMS": self.latent_sampler = 'UniPC' if self.hr_force or latent_scale_mode is not None: + shared.state.job = 'hires' if self.denoising_strength > 0: self.ops.append('hires') devices.torch_gc() # GC now before running the next img2img to prevent running out of memory @@ -1127,8 +1126,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): else: self.ops.append('upscale') x = None - shared.state.nextjob() self.is_hr_pass = False + shared.state.job = prev_job + shared.state.nextjob() return samples @@ -1293,6 +1293,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): samples = samples * self.nmask + self.init_latent * self.mask del x devices.torch_gc() + shared.state.nextjob() return samples def get_token_merging_ratio(self, for_hr=False): diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 7c3506715..832141fdf 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -63,14 +63,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro def diffusers_callback(step: int, _timestep: int, latents: torch.FloatTensor): shared.state.sampling_step = step - if p.is_hr_pass: - shared.state.job = 'hires' - shared.state.sampling_steps = p.hr_second_pass_steps # add optional hires - elif p.is_refiner_pass: - shared.state.job = 'refine' - shared.state.sampling_steps = calculate_refiner_steps() # add optional refiner - else: - shared.state.sampling_steps = p.steps # base steps shared.state.current_latent = latents if shared.state.interrupted or shared.state.skipped: raise AssertionError('Interrupted...') @@ -133,6 +125,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return encoded def vae_decode(latents, model, output_type='np', full_quality=True): + prev_job = shared.state.job + shared.state.job = 'vae' if not torch.is_tensor(latents): # already decoded return latents if latents.shape[0] == 0: @@ -150,6 +144,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro else: decoded = taesd_vae_decode(latents=latents) imgs = model.image_processor.postprocess(decoded, output_type=output_type) + shared.state.job = prev_job return imgs def vae_encode(image, model, full_quality=True): # pylint: disable=unused-variable @@ -388,6 +383,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro clip_skip=p.clip_skip, desc='Base', ) + shared.state.sampling_steps = base_args['num_inference_steps'] p.extra_generation_params['CFG rescale'] = p.diffusers_guidance_rescale p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1 else None try: @@ -403,6 +399,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if hasattr(shared.sd_model, 'embedding_db') and len(shared.sd_model.embedding_db.embeddings_used) > 0: p.extra_generation_params['Embeddings'] = ', '.join(shared.sd_model.embedding_db.embeddings_used) + shared.state.nextjob() if shared.state.interrupted or shared.state.skipped: return results @@ -412,10 +409,12 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if (hasattr(p, "hr_upscaler") and p.hr_upscaler is not None) else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") if p.is_hr_pass: p.init_hr() + prev_job = shared.state.job if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: p.ops.append('upscale') if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") + shared.state.job = 'upscale' output.images = hires_resize(latents=output.images) if latent_scale_mode is not None or p.hr_force: p.ops.append('hires') @@ -438,15 +437,22 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro strength=p.denoising_strength, desc='Hires', ) + shared.state.job = 'hires' + shared.state.sampling_steps = hires_args['num_inference_steps'] try: output = shared.sd_model(**hires_args) # pylint: disable=not-callable except AssertionError as e: shared.log.info(e) p.init_images = [] + shared.state.job = prev_job + shared.state.nextjob() p.is_hr_pass = False # optional refiner pass or decode if is_refiner_enabled: + prev_job = shared.state.job + shared.state.job = 'refine' + shared.state.job_count +=1 if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_refiner and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-refiner") if shared.opts.diffusers_move_base and not getattr(shared.sd_model, 'has_accelerate', False): @@ -491,6 +497,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro clip_skip=p.clip_skip, desc='Refiner', ) + shared.state.sampling_steps = refiner_args['num_inference_steps'] try: refiner_output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable except AssertionError as e: @@ -505,7 +512,9 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro shared.log.debug('Moving to CPU: model=refiner') shared.sd_refiner.to(devices.cpu) devices.torch_gc() - p.is_refiner_pass = True + shared.state.job = prev_job + shared.state.nextjob() + p.is_refiner_pass = False # final decode since there is no refiner if not is_refiner_enabled: diff --git a/modules/progress.py b/modules/progress.py index db32abb31..968c586b7 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -66,15 +66,20 @@ def progressapi(req: ProgressRequest): paused = shared.state.paused if not active: return InternalProgressResponse(job=shared.state.job, active=active, queued=queued, paused=paused, completed=completed, id_live_preview=-1, textinfo="Queued..." if queued else "Waiting...") - progress = 0 - if shared.state.job_count > 0: - progress += shared.state.job_no / shared.state.job_count - if shared.state.sampling_steps > 0 and shared.state.job_count > 0: - progress += 1 / (shared.state.job_count / 2 if shared.state.processing_has_refined_job_count else 1) * shared.state.sampling_step / shared.state.sampling_steps - progress = min(progress, 1) + if shared.state.job_no > shared.state.job_count: + shared.state.job_count = shared.state.job_no + batch_x = max(shared.state.job_no, 0) + batch_y = max(shared.state.job_count, 1) + step_x = max(shared.state.sampling_step, 0) + step_y = max(shared.state.sampling_steps, 1) + current = step_y * batch_x + step_x + total = step_y * batch_y + progress = min(1, current / total if total > 0 else 0) + elapsed_since_start = time.time() - shared.state.time_start predicted_duration = elapsed_since_start / progress if progress > 0 else None eta = predicted_duration - elapsed_since_start if predicted_duration is not None else None + id_live_preview = req.id_live_preview live_preview = None shared.state.set_current_image() @@ -83,4 +88,6 @@ def progressapi(req: ProgressRequest): shared.state.current_image.save(buffered, format='jpeg') live_preview = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' id_live_preview = shared.state.id_live_preview - return InternalProgressResponse(job=shared.state.job, active=active, queued=queued, paused=paused, completed=completed, progress=progress, eta=eta, live_preview=live_preview, id_live_preview=id_live_preview, textinfo=shared.state.textinfo) + + res = InternalProgressResponse(job=shared.state.job, active=active, queued=queued, paused=paused, completed=completed, progress=progress, eta=eta, live_preview=live_preview, id_live_preview=id_live_preview, textinfo=shared.state.textinfo) + return res diff --git a/modules/sd_models.py b/modules/sd_models.py index a8828f176..9aa30f388 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1155,7 +1155,7 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model') return None orig_state = copy.deepcopy(shared.state) shared.state = shared_state.State() - shared.state.begin(f'load-{op}') + shared.state.begin(f'load') if load_dict: shared.log.debug(f'Model dict: existing={sd_model is not None} target={checkpoint_info.filename} info={info}') else: diff --git a/modules/shared.py b/modules/shared.py index 9fa4c8e28..f7a295572 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -474,7 +474,7 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"), "schedulers_use_karras": OptionInfo(True, "Use Karras sigmas", gr.Checkbox, {"visible": False}), "schedulers_use_thresholding": OptionInfo(False, "Use dynamic thresholding", gr.Checkbox, {"visible": False}), "schedulers_use_loworder": OptionInfo(True, "Use simplified solvers in final steps", gr.Checkbox, {"visible": False}), - "schedulers_prediction_type": OptionInfo("default", "Override model prediction type", gr.Radio, {"choices": ['default', 'epsilon', 'sample', 'v_prediction'], "visible": False}), + "schedulers_prediction_type": OptionInfo("default", "Override model prediction type", gr.Radio, {"choices": ['default', 'epsilon', 'sample', 'v_prediction']}), # managed from ui.py for backend diffusers "schedulers_sep_diffusers": OptionInfo("

Diffusers specific config

", "", gr.HTML), diff --git a/modules/shared_state.py b/modules/shared_state.py index ad2d992d1..06378d7bf 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -13,7 +13,6 @@ class State: job_no = 0 job_count = 0 total_jobs = 0 - processing_has_refined_job_count = False job_timestamp = '0' sampling_step = 0 sampling_steps = 0 @@ -72,7 +71,6 @@ class State: self.job_no = 0 self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") self.paused = False - self.processing_has_refined_job_count = False self.sampling_step = 0 self.skipped = False self.textinfo = None diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 83aabcd17..d7105f8fb 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -425,7 +425,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st log_directory = f"{os.path.join(shared.cmd_opts.data_dir, 'train/log/embeddings')}" template_file = template_file.path - shared.state.job = "train-embedding" + shared.state.job = "train" shared.state.textinfo = "Initializing textual inversion training..." shared.state.job_count = steps diff --git a/scripts/loopback.py b/scripts/loopback.py index 413d3a6ee..cc4b5a6ee 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -90,7 +90,7 @@ class Script(scripts.Script): elif append_interrogation == "DeepBooru": p.prompt += deepbooru.model.tag(p.init_images[0]) - state.job = f"Iteration {i + 1}/{loops}, batch {n + 1}/{batch_count}" + state.job = f"loopback iteration {i+1}/{loops} batch {n+1}/{batch_count}" processed = processing.process_images(p) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 263a0ce09..d9ec0c02b 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -23,7 +23,6 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 out_fft = np.zeros((data.shape[0], data.shape[1]), dtype=np.complex128) out_fft[:, :] = np.fft.fft2(np.fft.fftshift(data), norm="ortho") out_fft[:, :] = np.fft.ifftshift(out_fft[:, :]) - return out_fft def _ifft2(data): @@ -37,13 +36,11 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 out_ifft = np.zeros((data.shape[0], data.shape[1]), dtype=np.complex128) out_ifft[:, :] = np.fft.ifft2(np.fft.fftshift(data), norm="ortho") out_ifft[:, :] = np.fft.ifftshift(out_ifft[:, :]) - return out_ifft def _get_gaussian_window(width, height, std=3.14, mode=0): window_scale_x = float(width / min(width, height)) window_scale_y = float(height / min(width, height)) - window = np.zeros((width, height)) x = (np.arange(width) / width * 2. - 1.) * window_scale_x for y in range(height): @@ -52,7 +49,6 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 window[:, y] = np.exp(-(x ** 2 + fy ** 2) * std) else: window[:, y] = (1 / ((x ** 2 + 1.) * (fy ** 2 + 1.))) ** (std / 3.14) # hey wait a minute that's not gaussian - return window def _get_masked_window_rgb(np_mask_grey, hardness=1.): @@ -64,57 +60,45 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 for c in range(3): np_mask_rgb[:, :, c] = hardened[:] return np_mask_rgb - width = _np_src_image.shape[0] height = _np_src_image.shape[1] num_channels = _np_src_image.shape[2] - _np_src_image[:] * (1. - np_mask_rgb) # pylint: disable=pointless-statement np_mask_grey = np.sum(np_mask_rgb, axis=2) / 3. img_mask = np_mask_grey > 1e-6 ref_mask = np_mask_grey < 1e-3 - windowed_image = _np_src_image * (1. - _get_masked_window_rgb(np_mask_grey)) windowed_image /= np.max(windowed_image) windowed_image += np.average(_np_src_image) * np_mask_rgb # / (1.-np.average(np_mask_rgb)) # rather than leave the masked area black, we get better results from fft by filling the average unmasked color - src_fft = _fft2(windowed_image) # get feature statistics from masked src img src_dist = np.absolute(src_fft) src_phase = src_fft / src_dist - # create a generator with a static seed to make outpainting deterministic / only follow global seed rng = np.random.default_rng(0) - noise_window = _get_gaussian_window(width, height, mode=1) # start with simple gaussian noise noise_rgb = rng.random((width, height, num_channels)) noise_grey = np.sum(noise_rgb, axis=2) / 3. noise_rgb *= color_variation # the colorfulness of the starting noise is blended to greyscale with a parameter for c in range(num_channels): noise_rgb[:, :, c] += (1. - color_variation) * noise_grey - noise_fft = _fft2(noise_rgb) for c in range(num_channels): noise_fft[:, :, c] *= noise_window noise_rgb = np.real(_ifft2(noise_fft)) shaped_noise_fft = _fft2(noise_rgb) shaped_noise_fft[:, :, :] = np.absolute(shaped_noise_fft[:, :, :]) ** 2 * (src_dist ** noise_q) * src_phase # perform the actual shaping - brightness_variation = 0. # color_variation contrast_adjusted_np_src = _np_src_image[:] * (brightness_variation + 1.) - brightness_variation * 2. - # scikit-image is used for histogram matching, very convenient! shaped_noise = np.real(_ifft2(shaped_noise_fft)) shaped_noise -= np.min(shaped_noise) shaped_noise /= np.max(shaped_noise) shaped_noise[img_mask, :] = skimage.exposure.match_histograms(shaped_noise[img_mask, :] ** 1., contrast_adjusted_np_src[ref_mask, :], channel_axis=1) shaped_noise = _np_src_image[:] * (1. - np_mask_rgb) + shaped_noise * np_mask_rgb - matched_noise = shaped_noise[:] - return np.clip(matched_noise, 0., 1.) - class Script(scripts.Script): def title(self): return "Outpainting" @@ -125,56 +109,43 @@ class Script(scripts.Script): def ui(self, is_img2img): if not is_img2img: return None - info = gr.HTML("

Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8

") - pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128, elem_id=self.elem_id("pixels")) mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=8, elem_id=self.elem_id("mask_blur")) direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'], elem_id=self.elem_id("direction")) noise_q = gr.Slider(label="Fall-off exponent (lower=higher detail)", minimum=0.0, maximum=4.0, step=0.01, value=1.0, elem_id=self.elem_id("noise_q")) color_variation = gr.Slider(label="Color variation", minimum=0.0, maximum=1.0, step=0.01, value=0.05, elem_id=self.elem_id("color_variation")) - return [info, pixels, mask_blur, direction, noise_q, color_variation] def run(self, p, _, pixels, mask_blur, direction, noise_q, color_variation): # pylint: disable=arguments-differ initial_seed_and_info = [None, None] - process_width = p.width process_height = p.height - p.mask_blur = mask_blur*4 p.inpaint_full_res = False p.inpainting_fill = 1 p.do_not_save_samples = True p.do_not_save_grid = True - left = pixels if "left" in direction else 0 right = pixels if "right" in direction else 0 up = pixels if "up" in direction else 0 down = pixels if "down" in direction else 0 - init_img = p.init_images[0] target_w = math.ceil((init_img.width + left + right) / 64) * 64 target_h = math.ceil((init_img.height + up + down) / 64) * 64 - if left > 0: left = left * (target_w - init_img.width) // (left + right) - if right > 0: right = target_w - init_img.width - left - if up > 0: up = up * (target_h - init_img.height) // (up + down) - if down > 0: down = target_h - init_img.height - up - def expand(init, count, expand_pixels, is_left=False, is_right=False, is_top=False, is_bottom=False): is_horiz = is_left or is_right is_vert = is_top or is_bottom pixels_horiz = expand_pixels if is_horiz else 0 pixels_vert = expand_pixels if is_vert else 0 - images_to_process = [] output_images = [] for n in range(count): @@ -182,7 +153,6 @@ class Script(scripts.Script): res_h = init[n].height + pixels_vert process_res_w = math.ceil(res_w / 64) * 64 process_res_h = math.ceil(res_h / 64) * 64 - img = Image.new("RGB", (process_res_w, process_res_h)) img.paste(init[n], (pixels_horiz if is_left else 0, pixels_vert if is_top else 0)) mask = Image.new("RGB", (process_res_w, process_res_h), "white") @@ -193,17 +163,14 @@ class Script(scripts.Script): mask.width - expand_pixels - mask_blur if is_right else res_w, mask.height - expand_pixels - mask_blur if is_bottom else res_h, ), fill="black") - np_image = (np.asarray(img) / 255.0).astype(np.float64) np_mask = (np.asarray(mask) / 255.0).astype(np.float64) noised = get_matched_noise(np_image, np_mask, noise_q, color_variation) output_images.append(Image.fromarray(np.clip(255.0 * noised, 0.0, 255.0).astype(np.uint8), mode="RGB")) - target_width = min(process_width, init[n].width + pixels_horiz) if is_horiz else img.width target_height = min(process_height, init[n].height + pixels_vert) if is_vert else img.height p.width = target_width if is_horiz else img.width p.height = target_height if is_vert else img.height - crop_region = ( 0 if is_left else output_images[n].width - target_width, 0 if is_top else output_images[n].height - target_height, @@ -212,12 +179,9 @@ class Script(scripts.Script): ) mask = mask.crop(crop_region) p.image_mask = mask - image_to_process = output_images[n].crop(crop_region) images_to_process.append(image_to_process) - p.init_images = images_to_process - latent_mask = Image.new("RGB", (p.width, p.height), "white") draw = ImageDraw.Draw(latent_mask) draw.rectangle(( @@ -227,29 +191,22 @@ class Script(scripts.Script): mask.height - expand_pixels - mask_blur * 2 if is_bottom else res_h, ), fill="black") p.latent_mask = latent_mask - proc = process_images(p) - if initial_seed_and_info[0] is None: initial_seed_and_info[0] = proc.seed initial_seed_and_info[1] = proc.info - for n in range(count): output_images[n].paste(proc.images[n], (0 if is_left else output_images[n].width - proc.images[n].width, 0 if is_top else output_images[n].height - proc.images[n].height)) output_images[n] = output_images[n].crop((0, 0, res_w, res_h)) - return output_images - batch_count = p.n_iter batch_size = p.batch_size p.n_iter = 1 state.job_count = batch_count * ((1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0)) all_processed_images = [] - for i in range(batch_count): imgs = [init_img] * batch_size - state.job = f"Batch {i + 1} out of {batch_count}" - + state.job = f"outpainting batch {i+1}/{batch_count}" if left > 0: imgs = expand(imgs, batch_size, left, is_left=True) if right > 0: @@ -258,22 +215,15 @@ class Script(scripts.Script): imgs = expand(imgs, batch_size, up, is_top=True) if down > 0: imgs = expand(imgs, batch_size, down, is_bottom=True) - all_processed_images += imgs - all_images = all_processed_images - combined_grid_image = images.image_grid(all_processed_images) if opts.return_grid and len(all_processed_images) > 1: all_images = [combined_grid_image] + all_processed_images - res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1]) - if opts.samples_save: for img in all_processed_images: images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.samples_format, info=res.info, p=p) - if opts.grid_save and len(all_processed_images) > 1: images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.samples_format, info=res.info, grid=True, p=p) - return res diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py index f52b2b615..3f6cbf335 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -22,40 +22,31 @@ class Script(scripts.Script): mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id=self.elem_id("mask_blur")) inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", elem_id=self.elem_id("inpainting_fill")) direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'], elem_id=self.elem_id("direction")) - return [pixels, mask_blur, inpainting_fill, direction] def run(self, p, pixels, mask_blur, inpainting_fill, direction): initial_seed = None initial_info = None - p.mask_blur = mask_blur * 2 p.inpainting_fill = inpainting_fill p.inpaint_full_res = False - left = pixels if "left" in direction else 0 right = pixels if "right" in direction else 0 up = pixels if "up" in direction else 0 down = pixels if "down" in direction else 0 - init_img = p.init_images[0] target_w = math.ceil((init_img.width + left + right) / 64) * 64 target_h = math.ceil((init_img.height + up + down) / 64) * 64 - if left > 0: left = left * (target_w - init_img.width) // (left + right) if right > 0: right = target_w - init_img.width - left - if up > 0: up = up * (target_h - init_img.height) // (up + down) - if down > 0: down = target_h - init_img.height - up - img = Image.new("RGB", (target_w, target_h)) img.paste(init_img, (left, up)) - mask = Image.new("L", (img.width, img.height), "white") draw = ImageDraw.Draw(mask) draw.rectangle(( @@ -64,7 +55,6 @@ class Script(scripts.Script): mask.width - right - (mask_blur * 2 if right > 0 else 0), mask.height - down - (mask_blur * 2 if down > 0 else 0) ), fill="black") - latent_mask = Image.new("L", (img.width, img.height), "white") latent_draw = ImageDraw.Draw(latent_mask) latent_draw.rectangle(( @@ -73,71 +63,50 @@ class Script(scripts.Script): mask.width - right - (mask_blur//2 if right > 0 else 0), mask.height - down - (mask_blur//2 if down > 0 else 0) ), fill="black") - devices.torch_gc() - grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels) grid_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels) grid_latent_mask = images.split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels) - p.n_iter = 1 p.batch_size = 1 p.do_not_save_grid = True p.do_not_save_samples = True - work = [] work_mask = [] work_latent_mask = [] work_results = [] - for (y, h, row), (_, _, row_mask), (_, _, row_latent_mask) in zip(grid.tiles, grid_mask.tiles, grid_latent_mask.tiles): for tiledata, tiledata_mask, tiledata_latent_mask in zip(row, row_mask, row_latent_mask): x, w = tiledata[0:2] - if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down: continue - work.append(tiledata[2]) work_mask.append(tiledata_mask[2]) work_latent_mask.append(tiledata_latent_mask[2]) - batch_count = len(work) log.info(f"Poor-man-outpainting: images={len(work)} tiles={len(grid.tiles[0][2])}x{len(grid.tiles)}.") - state.job_count = batch_count - for i in range(batch_count): p.init_images = [work[i]] p.image_mask = work_mask[i] p.latent_mask = work_latent_mask[i] - - state.job = f"Batch {i + 1} out of {batch_count}" + state.job = f"outpainting batch {i+1}/{batch_count}" processed = process_images(p) - if initial_seed is None: initial_seed = processed.seed initial_info = processed.info - p.seed = processed.seed + 1 work_results += processed.images - - image_index = 0 for y, h, row in grid.tiles: for tiledata in row: x, w = tiledata[0:2] - if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down: continue - tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) image_index += 1 - combined_image = images.combine_grid(grid) - if opts.samples_save: images.save_image(combined_image, p.outpath_samples, "", initial_seed, p.prompt, opts.samples_format, info=initial_info, p=p) - processed = Processed(p, [combined_image], initial_seed, initial_info) - return processed diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 8500ead07..0d35e262b 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -72,8 +72,7 @@ class Script(scripts.Script): for i in range(batch_count): p.batch_size = batch_size p.init_images = work[i * batch_size:(i + 1) * batch_size] - - state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}" + state.job = f"upscale batch {i+1+n*batch_count}/{state.job_count}" processed = processing.process_images(p) if initial_info is None: diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 3dbe72baf..99730b686 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -268,7 +268,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend def index(ix, iy, iz): return ix + iy * len(xs) + iz * len(xs) * len(ys) - shared.state.job = f"{index(ix, iy, iz) + 1} out of {list_size}" + shared.state.job = 'grid' processed: Processed = cell(x, y, z, ix, iy, iz) if processed_result is None: processed_result = copy(processed) diff --git a/webui.py b/webui.py index daab8d82b..b2477c1bc 100644 --- a/webui.py +++ b/webui.py @@ -157,7 +157,7 @@ def initialize(): def load_model(): if opts.sd_checkpoint_autoload: - shared.state.begin('load model') + shared.state.begin('load') thread_model = Thread(target=lambda: shared.sd_model) thread_model.start() thread_refiner = Thread(target=lambda: shared.sd_refiner)