| import modules.scripts as scripts
|
| import gradio as gr
|
|
|
| import io
|
| import json
|
| import matplotlib.pyplot as plt
|
| from PIL import Image
|
| import numpy as np
|
| import inspect
|
| import torch
|
| from modules import prompt_parser, devices, sd_samplers_common
|
| import re
|
| from modules.shared import opts, state
|
| import modules.shared as shared
|
| from modules.script_callbacks import CFGDenoiserParams, cfg_denoiser_callback
|
| from modules.script_callbacks import CFGDenoisedParams, cfg_denoised_callback
|
| from modules.script_callbacks import AfterCFGCallbackParams, cfg_after_cfg_callback
|
| import k_diffusion.utils as utils
|
| from k_diffusion.external import CompVisVDenoiser, CompVisDenoiser
|
| from modules.sd_samplers_timesteps import CompVisTimestepsDenoiser, CompVisTimestepsVDenoiser
|
| from modules.sd_samplers_cfg_denoiser import CFGDenoiser, catenate_conds, subscript_cond, pad_cond
|
| from modules import script_callbacks
|
| from scripts.CharaIte import Chara_iteration
|
|
|
| try:
|
| from modules_forge import forge_sampler
|
| isForge = True
|
| except Exception:
|
| isForge = False
|
|
|
|
|
| quote_swap = str.maketrans('\'"', '"\'')
|
|
|
|
|
| def pares_infotext(infotext, params):
|
|
|
| try:
|
| params['CHG'] = json.loads(params['CHG'].translate(quote_swap))
|
| except Exception:
|
| pass
|
|
|
|
|
| script_callbacks.on_infotext_pasted(pares_infotext)
|
|
|
|
|
|
|
| if not isForge:
|
| from scripts.webui_CHG import CHGdenoiserConstruct
|
| exec( CHGdenoiserConstruct() )
|
| else:
|
| from scripts.forge_CHG import CHGdenoiserConstruct
|
| import scripts.forge_CHG as forge_CHG
|
| exec( CHGdenoiserConstruct() )
|
|
|
|
|
|
|
| class ExtensionTemplateScript(scripts.Script):
|
|
|
| def title(self):
|
| return "Characteristic Guidance"
|
|
|
|
|
|
|
|
|
|
|
|
|
| def show(self, is_img2img):
|
| return scripts.AlwaysVisible
|
|
|
| def update_plot(self):
|
| from modules.sd_samplers_cfg_denoiser import CFGDenoiser
|
| try:
|
| res, ite_num, reg = CFGDenoiser.ite_infos
|
| res = np.array([r[:, 0, 0, 0].cpu().numpy() for r in res]).T
|
| ite_num = np.array([r.cpu().numpy() for r in ite_num]).T
|
| reg = np.array([r.cpu().numpy() for r in reg]).T
|
| if len(res) == 0:
|
| raise Exception('res has not been written yet')
|
| except Exception as e:
|
| res, ite_num, reg = [np.linspace(1, 0., 50)], [np.ones(50) * 10], [np.linspace(1, 0., 50)]
|
| print("The following exception occured when reading iteration info, demo plot is returned")
|
| print(e)
|
|
|
|
|
| try:
|
| res_thres = CFGDenoiser.res_thres
|
| reg_ini = CFGDenoiser.reg_ini
|
| reg_range = CFGDenoiser.reg_range
|
| noise_base = CFGDenoiser.noise_base
|
| start_step = CFGDenoiser.chg_start_step
|
| except:
|
| res_thres = 0.1
|
| reg_ini=1
|
| reg_range=1
|
| noise_base = 1
|
| start_step = 0
|
|
|
| from matplotlib.patches import Patch
|
| legend_elements = [Patch(facecolor='green', label='Converged'),
|
| Patch(facecolor='yellow', label='Barely Converged'),
|
| Patch(facecolor='red', label='Not Converged')]
|
| def get_title(reg_ini, reg_range, noise_base, num_no_converge, pos_no_converge):
|
| title = ""
|
| prompts = ["Nice! All iterations converged.\n ",
|
| "Lowering the regularization strength may be better.\n ",
|
| "One iteration not converge, but it is OK.\n ",
|
| "Two or more iteration not converge, maybe you should increase regularization strength.\n ",
|
| "Steps in the middle didn't converge, maybe you should increase regularization time range.\n ",
|
| "The regularization strength is already small. Increasing the number of basis worth a try.\n ",
|
| "If you think context changed too much, increase the regularization strength. \n ",
|
| "Increase the regularization strength may be better.\n ",
|
| "If you think context changed too little, lower the regularization strength. \n ",
|
| "If you think context changed too little, lower the regularization time range. \n ",
|
| "Number of Basis maybe too high, try lowering it. \n "
|
| ]
|
| if num_no_converge <=0:
|
| title += prompts[0]
|
| if num_no_converge <=0 and reg_ini > 0.5:
|
| title += prompts[1]
|
| if num_no_converge == 1:
|
| title += prompts[2]
|
| title += prompts[7]
|
| if num_no_converge >1:
|
| title += prompts[3]
|
| title += prompts[7]
|
| if pos_no_converge > 0.3:
|
| title += prompts[4]
|
| if num_no_converge <=0 and reg_ini <= 0.5:
|
| title += prompts[5]
|
| if num_no_converge <=0 and reg_ini < 5:
|
| title += prompts[6]
|
| if num_no_converge <=0 and reg_ini >= 5:
|
| title += prompts[8]
|
| title += prompts[9]
|
| if num_no_converge >=2 and noise_base >2:
|
| title += prompts[10]
|
| alltitles = title.split("\n")[:-1]
|
| n = np.random.randint(len(alltitles))
|
| return alltitles[n]
|
|
|
| fig, axs = plt.subplots(len(res), 1, figsize=(10, 4.5 * len(res)))
|
| if len(res) > 1:
|
|
|
| for i in range(len(res)):
|
| num_no_converge = 0
|
| pos_no_converge = 0
|
| for j, r in enumerate(res[i]):
|
| if r >= res_thres:
|
| num_no_converge+=1
|
| pos_no_converge = max(j,pos_no_converge)
|
| pos_no_converge = pos_no_converge/(len(res[i])+1)
|
|
|
| colors = ['green' if r < res_thres else 'yellow' if r < 10 * res_thres else 'red' for r in res[i]]
|
| axs[i].bar(np.arange(len(ite_num[i]))+start_step, ite_num[i], color=colors)
|
|
|
| axs[i].legend(handles=legend_elements, loc='upper right')
|
|
|
|
|
| axs[i].set_xlabel('Diffusion Step')
|
| axs[i].set_ylabel('Num. Characteristic Iteration')
|
| ax2 = axs[i].twinx()
|
| ax2.plot(np.arange(len(ite_num[i]))+start_step, reg[i], linewidth=4, color='C1', label='Regularization Level')
|
| ax2.set_ylabel('Regularization Level')
|
| ax2.set_ylim(bottom=0.)
|
| ax2.legend(loc='upper left')
|
| title = get_title(reg_ini, reg_range, noise_base, num_no_converge, pos_no_converge)
|
| ax2.set_title(title)
|
| ax2.autoscale()
|
|
|
| elif len(res) == 1:
|
| num_no_converge = 0
|
| pos_no_converge = 0
|
| for j, r in enumerate(res[0]):
|
| if r >= res_thres:
|
| num_no_converge+=1
|
| pos_no_converge = max(j,pos_no_converge)
|
| pos_no_converge = pos_no_converge/(len(res[0])+1)
|
| colors = ['green' if r < res_thres else 'yellow' if r < 10 * res_thres else 'red' for r in res[0]]
|
| axs.bar(np.arange(len(ite_num[0]))+start_step, ite_num[0], color=colors)
|
|
|
| axs.legend(handles=legend_elements, loc='upper right')
|
|
|
|
|
| axs.set_xlabel('Diffusion Step')
|
| axs.set_ylabel('Num. Characteristic Iteration')
|
| ax2 = axs.twinx()
|
| title = get_title(reg_ini, reg_range, noise_base, num_no_converge, pos_no_converge)
|
| ax2.plot(np.arange(len(ite_num[0]))+start_step, reg[0], linewidth=4, color='C1', label='Regularization Level')
|
| ax2.set_ylabel('Regularization Level')
|
| ax2.set_ylim(bottom=0.)
|
| ax2.legend(loc='upper left')
|
| ax2.set_title(title)
|
| ax2.autoscale()
|
| else:
|
| pass
|
|
|
|
|
| buf = io.BytesIO()
|
| plt.savefig(buf, format='png')
|
| buf.seek(0)
|
| img = Image.open(buf)
|
|
|
| plt.close()
|
| return img
|
|
|
|
|
| def ui(self, is_img2img):
|
| with gr.Accordion('Characteristic Guidance (CHG)', open=False):
|
| reg_ini = gr.Slider(
|
| minimum=0.0,
|
| maximum=10.,
|
| step=0.1,
|
| value=1.,
|
| label="Regularization Strength ( → Easier Convergence, Closer to Classfier-Free. Please try various values)",
|
| )
|
| reg_range = gr.Slider(
|
| minimum=0.01,
|
| maximum=10.,
|
| step=0.01,
|
| value=1.,
|
| label="Regularization Range Over Time ( ← Harder Convergence, More Correction. Please try various values)",
|
| )
|
| ite = gr.Slider(
|
| minimum=1,
|
| maximum=50,
|
| step=1,
|
| value=50,
|
| label="Max Num. Characteristic Iteration ( → Slow but Better Convergence)",
|
| )
|
| noise_base = gr.Slider(
|
| minimum=0,
|
| maximum=10,
|
| step=1,
|
| value=0,
|
| label="Num. Basis for Correction ( ← Less Correction, Better Convergence)",
|
| )
|
| with gr.Row(open=True):
|
| start_step = gr.Slider(
|
| minimum=0.0,
|
| maximum=0.25,
|
| step=0.01,
|
| value=0.0,
|
| label="CHG Start Step ( Use CFG before Percent of Steps. )",
|
| )
|
| stop_step = gr.Slider(
|
| minimum=0.25,
|
| maximum=1.0,
|
| step=0.01,
|
| value=1.0,
|
| label="CHG End Step ( Use CFG after Percent of Steps. )",
|
| )
|
| with gr.Accordion('Advanced', open=False):
|
| chara_decay = gr.Slider(
|
| minimum=0.,
|
| maximum=1.,
|
| step=0.01,
|
| value=1.,
|
| label="Reuse Correction of Previous Iteration ( → Suppress Abrupt Changes During Generation )",
|
| )
|
| res = gr.Slider(
|
| minimum=-6,
|
| maximum=-2,
|
| step=0.1,
|
| value=-4.,
|
| label="Log 10 Tolerance for Iteration Convergence ( → Faster Convergence, Lower Quality)",
|
| )
|
| lr = gr.Slider(
|
| minimum=0,
|
| maximum=1,
|
| step=0.01,
|
| value=1.,
|
| label="Iteration Step Size ( → Faster Convergence)",
|
| )
|
| reg_size = gr.Slider(
|
| minimum=0.0,
|
| maximum=1.,
|
| step=0.1,
|
| value=0.4,
|
| label="Regularization Annealing Speed ( ← Slower, Maybe Easier Convergence)",
|
| )
|
| reg_w = gr.Slider(
|
| minimum=0.0,
|
| maximum=5,
|
| step=0.01,
|
| value=0.5,
|
| label="Regularization Annealing Strength ( ← Stronger Annealing, Slower, Maybe Better Convergence )",
|
| )
|
| aa_dim = gr.Slider(
|
| minimum=1,
|
| maximum=10,
|
| step=1,
|
| value=2,
|
| label="AA Iteration Memory Size ( → Faster Convergence, Maybe Unstable)",
|
| )
|
| with gr.Row():
|
| checkbox = gr.Checkbox(
|
| False,
|
| label="Enable"
|
| )
|
| markdown = gr.Markdown("[How to set parameters? Check our github!](https://github.com/scraed/CharacteristicGuidanceWebUI/tree/main)")
|
| radio = gr.Radio(
|
| choices=["More Prompt", "More ControlNet"],
|
| label="ControlNet Compatible Mode",
|
| value = "More ControlNet"
|
| )
|
| with gr.Blocks() as demo:
|
| image = gr.Image()
|
| button = gr.Button("Check Convergence (Please Adjust Regularization Strength & Range Over Time If Not Converged)")
|
|
|
| button.click(fn=self.update_plot, outputs=image)
|
|
|
|
|
|
|
|
|
|
|
| def get_chg_parameter(key, default=None):
|
| def get_parameters(d):
|
| return d.get('CHG', {}).get(key, default)
|
| return get_parameters
|
|
|
|
|
| self.infotext_fields = [
|
| (checkbox, lambda d: 'CHG' in d),
|
| (reg_ini, get_chg_parameter('RegS')),
|
| (reg_range, get_chg_parameter('RegR')),
|
| (ite, get_chg_parameter('MaxI')),
|
| (noise_base, get_chg_parameter('NBasis')),
|
| (chara_decay, get_chg_parameter('Reuse')),
|
| (res, get_chg_parameter('Tol')),
|
| (lr, get_chg_parameter('IteSS')),
|
| (reg_size, get_chg_parameter('ASpeed')),
|
| (reg_w, get_chg_parameter('AStrength')),
|
| (aa_dim, get_chg_parameter('AADim')),
|
| (radio, get_chg_parameter('CMode')),
|
| (start_step, get_chg_parameter('StartStep')),
|
| (stop_step, get_chg_parameter('StopStep'))
|
| ]
|
|
|
|
|
| return [reg_ini, reg_range, ite, noise_base, chara_decay, res, lr, reg_size, reg_w, aa_dim, checkbox, markdown, radio, start_step, stop_step]
|
|
|
| def process(self, p, reg_ini, reg_range, ite, noise_base, chara_decay, res, lr, reg_size, reg_w, aa_dim,
|
| checkbox, markdown, radio, start_step, stop_step, **kwargs):
|
| if checkbox:
|
|
|
|
|
| parameters = {
|
| 'RegS': reg_ini,
|
| 'RegR': reg_range,
|
| 'MaxI': ite,
|
| 'NBasis': noise_base,
|
| 'Reuse': chara_decay,
|
| 'Tol': res,
|
| 'IteSS': lr,
|
| 'ASpeed': reg_size,
|
| 'AStrength': reg_w,
|
| 'AADim': aa_dim,
|
| 'CMode': radio,
|
| 'StartStep': start_step,
|
| 'StopStep': stop_step,
|
| }
|
| p.extra_generation_params["CHG"] = json.dumps(parameters).translate(quote_swap)
|
| print("Characteristic Guidance parameters registered")
|
|
|
|
|
|
|
|
|
| def process_batch(self, p, reg_ini, reg_range, ite, noise_base, chara_decay, res, lr, reg_size, reg_w, aa_dim,
|
| checkbox, markdown, radio, start_step, stop_step, **kwargs):
|
| def modified_sample(sample):
|
| def wrapper(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts):
|
|
|
| if checkbox:
|
|
|
| print("Characteristic Guidance modifying the CFGDenoiser")
|
|
|
|
|
| original_forward = CFGDenoiser.forward
|
| def _call_forward(self, *args, **kwargs):
|
| if self.chg_start_step <= self.step < self.chg_stop_step:
|
| return CHGDenoiser.forward(self, *args, **kwargs)
|
| else:
|
| return original_forward(self, *args, **kwargs)
|
| CFGDenoiser.forward = _call_forward
|
|
|
| print('*********cfg denoiser res thres def ************')
|
| CFGDenoiser.res_thres = 10 ** res
|
| CFGDenoiser.noise_base = noise_base
|
| CFGDenoiser.lr_chara = lr
|
| CFGDenoiser.ite = ite
|
| CFGDenoiser.reg_size = reg_size
|
| if reg_ini<=5:
|
| CFGDenoiser.reg_ini = reg_ini
|
| else:
|
| k = 0.8898
|
| CFGDenoiser.reg_ini = np.exp(k*(reg_ini-5))/np.exp(0)/k + 5 - 1/k
|
| if reg_range<=5:
|
| CFGDenoiser.reg_range = reg_range
|
| else:
|
| k = 0.8898
|
| CFGDenoiser.reg_range = np.exp(k*(reg_range-5))/np.exp(0)/k + 5 - 1/k
|
| CFGDenoiser.reg_w = reg_w
|
| CFGDenoiser.ite_infos = [[], [], []]
|
| CFGDenoiser.dxs_buffer = None
|
| CFGDenoiser.abt_buffer = None
|
| CFGDenoiser.aa_dim = aa_dim
|
| CFGDenoiser.chara_decay = chara_decay
|
| CFGDenoiser.process_p = p
|
| CFGDenoiser.radio_controlnet = radio
|
| constrain_step = lambda total_step, step_pct: max(0, min(round(total_step * step_pct), total_step))
|
| CFGDenoiser.chg_start_step = constrain_step(p.steps, start_step)
|
| CFGDenoiser.chg_stop_step = constrain_step(p.steps, stop_step)
|
|
|
| try:
|
| print("Characteristic Guidance sampling:")
|
| result = sample(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength,
|
| prompts)
|
|
|
| except Exception as e:
|
| raise e
|
| finally:
|
| print("Characteristic Guidance recorded iterations info for " + str(len(CFGDenoiser.ite_infos[0])) + " steps" )
|
| print("Characteristic Guidance recovering the CFGDenoiser")
|
| CFGDenoiser.forward = original_forward
|
|
|
| else:
|
| result = sample(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength,
|
| prompts)
|
| return result
|
|
|
| return wrapper
|
|
|
|
|
| if checkbox:
|
| print("Characteristic Guidance enabled, warpping the sample method")
|
| p.sample = modified_sample(p.sample).__get__(p)
|
|
|
|
|
|
|
|
|