xy_grid.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. from collections import namedtuple
  2. from copy import copy
  3. from itertools import permutations, chain
  4. import random
  5. import csv
  6. from io import StringIO
  7. from PIL import Image
  8. import numpy as np
  9. import modules.scripts as scripts
  10. import gradio as gr
  11. from modules import images
  12. from modules.processing import process_images, Processed
  13. from modules.shared import opts, cmd_opts, state
  14. import modules.shared as shared
  15. import modules.sd_samplers
  16. import modules.sd_models
  17. import re
  18. def apply_field(field):
  19. def fun(p, x, xs):
  20. setattr(p, field, x)
  21. return fun
  22. def apply_prompt(p, x, xs):
  23. p.prompt = p.prompt.replace(xs[0], x)
  24. p.negative_prompt = p.negative_prompt.replace(xs[0], x)
  25. def apply_order(p, x, xs):
  26. token_order = []
  27. # Initally grab the tokens from the prompt, so they can be replaced in order of earliest seen
  28. for token in x:
  29. token_order.append((p.prompt.find(token), token))
  30. token_order.sort(key=lambda t: t[0])
  31. prompt_parts = []
  32. # Split the prompt up, taking out the tokens
  33. for _, token in token_order:
  34. n = p.prompt.find(token)
  35. prompt_parts.append(p.prompt[0:n])
  36. p.prompt = p.prompt[n + len(token):]
  37. # Rebuild the prompt with the tokens in the order we want
  38. prompt_tmp = ""
  39. for idx, part in enumerate(prompt_parts):
  40. prompt_tmp += part
  41. prompt_tmp += x[idx]
  42. p.prompt = prompt_tmp + p.prompt
  43. samplers_dict = {}
  44. for i, sampler in enumerate(modules.sd_samplers.samplers):
  45. samplers_dict[sampler.name.lower()] = i
  46. for alias in sampler.aliases:
  47. samplers_dict[alias.lower()] = i
  48. def apply_sampler(p, x, xs):
  49. sampler_index = samplers_dict.get(x.lower(), None)
  50. if sampler_index is None:
  51. raise RuntimeError(f"Unknown sampler: {x}")
  52. p.sampler_index = sampler_index
  53. def apply_checkpoint(p, x, xs):
  54. info = modules.sd_models.get_closet_checkpoint_match(x)
  55. assert info is not None, f'Checkpoint for {x} not found'
  56. modules.sd_models.reload_model_weights(shared.sd_model, info)
  57. def apply_hypernetwork(p, x, xs):
  58. shared.hypernetwork = shared.hypernetworks.get(x, None)
  59. def format_value_add_label(p, opt, x):
  60. if type(x) == float:
  61. x = round(x, 8)
  62. return f"{opt.label}: {x}"
  63. def format_value(p, opt, x):
  64. if type(x) == float:
  65. x = round(x, 8)
  66. return x
  67. def format_value_join_list(p, opt, x):
  68. return ", ".join(x)
  69. def do_nothing(p, x, xs):
  70. pass
  71. def format_nothing(p, opt, x):
  72. return ""
  73. def str_permutations(x):
  74. """dummy function for specifying it in AxisOption's type when you want to get a list of permutations"""
  75. return x
  76. AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value"])
  77. AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value"])
  78. axis_options = [
  79. AxisOption("Nothing", str, do_nothing, format_nothing),
  80. AxisOption("Seed", int, apply_field("seed"), format_value_add_label),
  81. AxisOption("Var. seed", int, apply_field("subseed"), format_value_add_label),
  82. AxisOption("Var. strength", float, apply_field("subseed_strength"), format_value_add_label),
  83. AxisOption("Steps", int, apply_field("steps"), format_value_add_label),
  84. AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label),
  85. AxisOption("Prompt S/R", str, apply_prompt, format_value),
  86. AxisOption("Prompt order", str_permutations, apply_order, format_value_join_list),
  87. AxisOption("Sampler", str, apply_sampler, format_value),
  88. AxisOption("Checkpoint name", str, apply_checkpoint, format_value),
  89. AxisOption("Hypernetwork", str, apply_hypernetwork, format_value),
  90. AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label),
  91. AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label),
  92. AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label),
  93. AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label),
  94. AxisOption("Eta", float, apply_field("eta"), format_value_add_label),
  95. AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label), # as it is now all AxisOptionImg2Img items must go after AxisOption ones
  96. ]
  97. def draw_xy_grid(p, xs, ys, x_labels, y_labels, cell, draw_legend):
  98. res = []
  99. ver_texts = [[images.GridAnnotation(y)] for y in y_labels]
  100. hor_texts = [[images.GridAnnotation(x)] for x in x_labels]
  101. first_pocessed = None
  102. state.job_count = len(xs) * len(ys) * p.n_iter
  103. for iy, y in enumerate(ys):
  104. for ix, x in enumerate(xs):
  105. state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}"
  106. processed = cell(x, y)
  107. if first_pocessed is None:
  108. first_pocessed = processed
  109. try:
  110. res.append(processed.images[0])
  111. except:
  112. res.append(Image.new(res[0].mode, res[0].size))
  113. grid = images.image_grid(res, rows=len(ys))
  114. if draw_legend:
  115. grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts)
  116. first_pocessed.images = [grid]
  117. return first_pocessed
  118. re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*")
  119. re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*")
  120. re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*")
  121. re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*")
  122. class Script(scripts.Script):
  123. def title(self):
  124. return "X/Y plot"
  125. def ui(self, is_img2img):
  126. current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img]
  127. with gr.Row():
  128. x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, visible=False, type="index", elem_id="x_type")
  129. x_values = gr.Textbox(label="X values", visible=False, lines=1)
  130. with gr.Row():
  131. y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[4].label, visible=False, type="index", elem_id="y_type")
  132. y_values = gr.Textbox(label="Y values", visible=False, lines=1)
  133. draw_legend = gr.Checkbox(label='Draw legend', value=True)
  134. no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False)
  135. return [x_type, x_values, y_type, y_values, draw_legend, no_fixed_seeds]
  136. def run(self, p, x_type, x_values, y_type, y_values, draw_legend, no_fixed_seeds):
  137. modules.processing.fix_seed(p)
  138. p.batch_size = 1
  139. initial_hn = shared.hypernetwork
  140. def process_axis(opt, vals):
  141. if opt.label == 'Nothing':
  142. return [0]
  143. valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals)))]
  144. if opt.type == int:
  145. valslist_ext = []
  146. for val in valslist:
  147. m = re_range.fullmatch(val)
  148. mc = re_range_count.fullmatch(val)
  149. if m is not None:
  150. start = int(m.group(1))
  151. end = int(m.group(2))+1
  152. step = int(m.group(3)) if m.group(3) is not None else 1
  153. valslist_ext += list(range(start, end, step))
  154. elif mc is not None:
  155. start = int(mc.group(1))
  156. end = int(mc.group(2))
  157. num = int(mc.group(3)) if mc.group(3) is not None else 1
  158. valslist_ext += [int(x) for x in np.linspace(start=start, stop=end, num=num).tolist()]
  159. else:
  160. valslist_ext.append(val)
  161. valslist = valslist_ext
  162. elif opt.type == float:
  163. valslist_ext = []
  164. for val in valslist:
  165. m = re_range_float.fullmatch(val)
  166. mc = re_range_count_float.fullmatch(val)
  167. if m is not None:
  168. start = float(m.group(1))
  169. end = float(m.group(2))
  170. step = float(m.group(3)) if m.group(3) is not None else 1
  171. valslist_ext += np.arange(start, end + step, step).tolist()
  172. elif mc is not None:
  173. start = float(mc.group(1))
  174. end = float(mc.group(2))
  175. num = int(mc.group(3)) if mc.group(3) is not None else 1
  176. valslist_ext += np.linspace(start=start, stop=end, num=num).tolist()
  177. else:
  178. valslist_ext.append(val)
  179. valslist = valslist_ext
  180. elif opt.type == str_permutations:
  181. valslist = list(permutations(valslist))
  182. valslist = [opt.type(x) for x in valslist]
  183. return valslist
  184. x_opt = axis_options[x_type]
  185. xs = process_axis(x_opt, x_values)
  186. y_opt = axis_options[y_type]
  187. ys = process_axis(y_opt, y_values)
  188. def fix_axis_seeds(axis_opt, axis_list):
  189. if axis_opt.label == 'Seed':
  190. return [int(random.randrange(4294967294)) if val is None or val == '' or val == -1 else val for val in axis_list]
  191. else:
  192. return axis_list
  193. if not no_fixed_seeds:
  194. xs = fix_axis_seeds(x_opt, xs)
  195. ys = fix_axis_seeds(y_opt, ys)
  196. if x_opt.label == 'Steps':
  197. total_steps = sum(xs) * len(ys)
  198. elif y_opt.label == 'Steps':
  199. total_steps = sum(ys) * len(xs)
  200. else:
  201. total_steps = p.steps * len(xs) * len(ys)
  202. print(f"X/Y plot will create {len(xs) * len(ys) * p.n_iter} images on a {len(xs)}x{len(ys)} grid. (Total steps to process: {total_steps * p.n_iter})")
  203. shared.total_tqdm.updateTotal(total_steps * p.n_iter)
  204. def cell(x, y):
  205. pc = copy(p)
  206. x_opt.apply(pc, x, xs)
  207. y_opt.apply(pc, y, ys)
  208. return process_images(pc)
  209. processed = draw_xy_grid(
  210. p,
  211. xs=xs,
  212. ys=ys,
  213. x_labels=[x_opt.format_value(p, x_opt, x) for x in xs],
  214. y_labels=[y_opt.format_value(p, y_opt, y) for y in ys],
  215. cell=cell,
  216. draw_legend=draw_legend
  217. )
  218. if opts.grid_save:
  219. images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, grid=True, p=p)
  220. # restore checkpoint in case it was changed by axes
  221. modules.sd_models.reload_model_weights(shared.sd_model)
  222. shared.hypernetwork = initial_hn
  223. return processed