From faa0bf53580ac910cb26d415f21933c8c55addfb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 30 Oct 2024 12:26:07 -0400 Subject: [PATCH] add xyz optional time info Signed-off-by: Vladimir Mandic --- modules/images.py | 10 +++++++++- modules/images_grid.py | 18 +++++++++--------- scripts/xyz_grid.py | 29 +++++++++++++++-------------- scripts/xyz_grid_classes.py | 16 ++++++++-------- scripts/xyz_grid_draw.py | 22 ++++++++++++++++------ scripts/xyz_grid_on.py | 12 +++++++----- 6 files changed, 64 insertions(+), 43 deletions(-) diff --git a/modules/images.py b/modules/images.py index add7c5c7d..8e7ffd662 100644 --- a/modules/images.py +++ b/modules/images.py @@ -10,7 +10,7 @@ import threading import numpy as np import piexif import piexif.helper -from PIL import Image, PngImagePlugin, ExifTags +from PIL import Image, PngImagePlugin, ExifTags, ImageDraw from modules import sd_samplers, shared, script_callbacks, errors, paths from modules.images_grid import image_grid, get_grid_size, split_grid, combine_grid, check_grid_size, get_font, draw_grid_annotations, draw_prompt_matrix, GridAnnotation, Grid # pylint: disable=unused-import from modules.images_resize import resize_image # pylint: disable=unused-import @@ -361,6 +361,14 @@ def flatten(img, bgcolor): return img.convert('RGB') +def draw_overlay(im, text): + d = ImageDraw.Draw(im) + fontsize = (im.width + im.height) // 50 + font = get_font(fontsize) + d.text((fontsize//2, fontsize//2), text, font=font, fill=shared.opts.font_color) + return im + + def set_watermark(image, watermark): if shared.opts.image_watermark_position != 'none': # visible watermark wm_image = None diff --git a/modules/images_grid.py b/modules/images_grid.py index 194b0d1cd..51371a0fe 100644 --- a/modules/images_grid.py +++ b/modules/images_grid.py @@ -113,7 +113,7 @@ def get_font(fontsize): return ImageFont.truetype("javascript/notosans-nerdfont-regular.ttf", fontsize) -def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, title=None): +def draw_grid_annotations(im, width, height, x_texts, y_texts, margin=0, title=None): def wrap(drawing, text, font, line_length): lines = [''] for word in text.split(): @@ -140,15 +140,15 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit line_spacing = fontsize // 2 font = get_font(fontsize) color_inactive = (127, 127, 127) - pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4 - cols = len(hor_texts) - rows = len(ver_texts) + pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in y_texts]) == 0 else width * 3 // 4 + cols = len(x_texts) + rows = len(y_texts) # assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' # assert rows == len(hor_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' calc_img = Image.new("RGB", (1, 1), shared.opts.grid_background) calc_d = ImageDraw.Draw(calc_img) title_texts = [title] if title else [[GridAnnotation()]] - for texts, allowed_width in zip(hor_texts + ver_texts + title_texts, [width] * len(hor_texts) + [pad_left] * len(ver_texts) + [(width+margin)*cols]): + for texts, allowed_width in zip(x_texts + y_texts + title_texts, [width] * len(x_texts) + [pad_left] * len(y_texts) + [(width+margin)*cols]): items = [] + texts texts.clear() for line in items: @@ -158,8 +158,8 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit bbox = calc_d.multiline_textbbox((0, 0), line.text, font=font) line.size = (bbox[2] - bbox[0], bbox[3] - bbox[1]) line.allowed_width = allowed_width - hor_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing for lines in hor_texts] - ver_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing * len(lines) for lines in ver_texts] + hor_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing for lines in x_texts] + ver_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing * len(lines) for lines in y_texts] pad_top = 0 if sum(hor_text_heights) == 0 else max(hor_text_heights) + line_spacing * 2 title_pad = 0 if title: @@ -178,11 +178,11 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit for col in range(cols): x = pad_left + (width + margin) * col + width / 2 y = (pad_top / 2 - hor_text_heights[col] / 2) + title_pad - draw_texts(d, x, y, hor_texts[col], font, fontsize) + draw_texts(d, x, y, x_texts[col], font, fontsize) for row in range(rows): x = pad_left / 2 y = (pad_top + (height + margin) * row + height / 2 - ver_text_heights[row] / 2) + title_pad - draw_texts(d, x, y, ver_texts[row], font, fontsize) + draw_texts(d, x, y, y_texts[row], font, fontsize) return result diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index e8f649182..1ed305dd7 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -48,10 +48,11 @@ class Script(scripts.Script): csv_mode = gr.Checkbox(label='Text inputs', value=False, elem_id=self.elem_id("csv_mode"), container=False) draw_legend = gr.Checkbox(label='Legend', value=True, elem_id=self.elem_id("draw_legend"), container=False) no_fixed_seeds = gr.Checkbox(label='Random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"), container=False) + include_time = gr.Checkbox(label='Add time info', value=False, elem_id=self.elem_id("include_time"), container=False) with gr.Column(): - no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid"), container=False) - include_lone_images = gr.Checkbox(label='Sub-images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) - include_sub_grids = gr.Checkbox(label='Sub-grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_grid = gr.Checkbox(label='Include main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) + include_subgrids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_images = gr.Checkbox(label='Include images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) with gr.Row(): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(): @@ -130,10 +131,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), ) - return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size] + return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size] - def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size): # pylint: disable=W0221 - shared.log.debug(f'xyzgrid: x_type={x_type}|x_values={x_values}|x_values_dropdown={x_values_dropdown}|y_type={y_type}|{y_values}={y_values}|{y_values_dropdown}={y_values_dropdown}|z_type={z_type}|z_values={z_values}|z_values_dropdown={z_values_dropdown}|draw_legend={draw_legend}|include_lone_images={include_lone_images}|include_sub_grids={include_sub_grids}|no_grid={no_grid}|margin_size={margin_size}') + def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size): # pylint: disable=W0221 if not no_fixed_seeds: processing.fix_seed(p) if not shared.opts.return_grid: @@ -307,38 +307,39 @@ class Script(scripts.Script): z_labels=[z_opt.format_value(p, z_opt, z) for z in zs], cell=cell, draw_legend=draw_legend, - include_lone_images=include_lone_images, - include_sub_grids=include_sub_grids, + include_lone_images=include_images, + include_sub_grids=include_subgrids, first_axes_processed=first_axes_processed, second_axes_processed=second_axes_processed, margin_size=margin_size, - no_grid=no_grid, + no_grid=not include_grid, + include_time=include_time, ) if not processed.images: return processed # It broke, no further handling needed. z_count = len(zs) processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) - if not include_lone_images: + if not include_images: # Don't need sub-images anymore, drop from list: - if no_grid and include_sub_grids: + if not include_grid and include_subgrids: processed.images = processed.images[:z_count] # we don't have the main grid image, and need zero additional sub-images else: processed.images = processed.images[:z_count+1] # we either have the main grid image, or need one sub-images if shared.opts.grid_save: # Auto-save main and sub-grids: - grid_count = z_count + ( 1 if not no_grid and z_count > 1 else 0 ) + grid_count = z_count + ( 1 if include_grid and z_count > 1 else 0 ) for g in range(grid_count): adj_g = g-1 if g > 0 else g info = processed.infotexts[g] prompt = processed.all_prompts[adj_g] seed = processed.all_seeds[adj_g] images.save_image(processed.images[g], p.outpath_grids, "grid", info=info, extension=shared.opts.grid_format, prompt=prompt, seed=seed, grid=True, p=processed) - if not include_sub_grids: # Done with sub-grids, drop all related information: + if not include_subgrids: # Done with sub-grids, drop all related information: for _sg in range(z_count): del processed.images[1] del processed.all_prompts[1] del processed.all_seeds[1] del processed.infotexts[1] - elif no_grid: + elif include_grid: del processed.infotexts[0] return processed diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index 335d66186..202482157 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -84,13 +84,13 @@ class SharedSettingsStackHelper(object): axis_options = [ AxisOption("Nothing", str, do_nothing, fmt=format_nothing), - AxisOption("[Model] Model", str, apply_checkpoint, cost=1.0, fmt=format_value, choices=lambda: sorted(sd_models.checkpoints_list)), + AxisOption("[Model] Model", str, apply_checkpoint, cost=1.0, fmt=format_value_add_label, choices=lambda: sorted(sd_models.checkpoints_list)), AxisOption("[Model] UNET", str, apply_unet, cost=0.8, choices=lambda: ['None'] + list(sd_unet.unet_dict)), AxisOption("[Model] VAE", str, apply_vae, cost=0.6, choices=lambda: ['None'] + list(sd_vae.vae_dict)), - AxisOption("[Model] Refiner", str, apply_refiner, cost=0.8, fmt=format_value, choices=lambda: ['None'] + sorted(sd_models.checkpoints_list)), + AxisOption("[Model] Refiner", str, apply_refiner, cost=0.8, fmt=format_value_add_label, choices=lambda: ['None'] + sorted(sd_models.checkpoints_list)), AxisOption("[Model] Text encoder", str, apply_te, cost=0.7, choices=shared_items.sd_te_items), - AxisOption("[Model] Dictionary", str, apply_dict, fmt=format_value, cost=0.9, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), - AxisOption("[Prompt] Search & replace", str, apply_prompt, fmt=format_value), + AxisOption("[Model] Dictionary", str, apply_dict, fmt=format_value_add_label, cost=0.9, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), + AxisOption("[Prompt] Search & replace", str, apply_prompt, fmt=format_value_add_label), AxisOption("[Prompt] Prompt order", str_permutations, apply_order, fmt=format_value_join_list), AxisOption("[Network] LoRA", str, apply_lora, cost=0.5, choices=list_lora), AxisOption("[Network] LoRA strength", float, apply_setting('extra_networks_default_multiplier')), @@ -109,8 +109,8 @@ axis_options = [ AxisOption("[Process] Model args", str, apply_task_args), AxisOption("[Process] Processing args", str, apply_processing), AxisOption("[Process] Server options", str, apply_options), - AxisOptionTxt2Img("[Sampler] Name", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), - AxisOptionImg2Img("[Sampler] Name", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers_for_img2img]), + AxisOptionTxt2Img("[Sampler] Name", str, apply_sampler, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOptionImg2Img("[Sampler] Name", str, apply_sampler, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers_for_img2img]), AxisOption("[Sampler] Sigma method", str, apply_setting("schedulers_sigma"), choices=lambda: ['default', 'karras', 'beta', 'exponential']), AxisOption("[Sampler] Timestep spacing", str, apply_setting("schedulers_timestep_spacing"), choices=lambda: ['default', 'linspace', 'leading', 'trailing']), AxisOption("[Sampler] Timestep range", int, apply_setting("schedulers_timesteps_range")), @@ -122,7 +122,7 @@ axis_options = [ AxisOption("[Sampler] eta delta", float, apply_setting("eta_noise_seed_delta")), AxisOption("[Sampler] eta multiplier", float, apply_setting("scheduler_eta")), AxisOption("[Refine] Upscaler", str, apply_field("hr_upscaler"), cost=0.3, choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), - AxisOption("[Refine] Sampler", str, apply_hr_sampler_name, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOption("[Refine] Sampler", str, apply_hr_sampler_name, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), AxisOption("[Refine] Denoising strength", float, apply_field("denoising_strength")), AxisOption("[Refine] Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("[Refine] Guidance scale", float, apply_field("image_cfg_scale")), @@ -131,7 +131,7 @@ axis_options = [ AxisOption("[Refine] Refiner steps", float, apply_field("refiner_steps")), AxisOption("[Postprocess] Upscaler", str, apply_upscaler, cost=0.4, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), AxisOption("[Postprocess] Context", str, apply_context, choices=lambda: ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"]), - AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value), + AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value_add_label), AxisOption("[HDR] Mode", int, apply_field("hdr_mode")), AxisOption("[HDR] Brightness", float, apply_field("hdr_brightness")), AxisOption("[HDR] Color", float, apply_field("hdr_color")), diff --git a/scripts/xyz_grid_draw.py b/scripts/xyz_grid_draw.py index 8f95e696b..9a9f2246c 100644 --- a/scripts/xyz_grid_draw.py +++ b/scripts/xyz_grid_draw.py @@ -4,23 +4,28 @@ from PIL import Image from modules import shared, images, processing -def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size, no_grid): # pylint: disable=unused-argument - hor_texts = [[images.GridAnnotation(x)] for x in x_labels] - ver_texts = [[images.GridAnnotation(y)] for y in y_labels] - title_texts = [[images.GridAnnotation(z)] for z in z_labels] +def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size, no_grid: False, include_time: False): # pylint: disable=unused-argument + x_texts = [[images.GridAnnotation(x)] for x in x_labels] + y_texts = [[images.GridAnnotation(y)] for y in y_labels] + z_texts = [[images.GridAnnotation(z)] for z in z_labels] list_size = (len(xs) * len(ys) * len(zs)) processed_result = None shared.state.job_count = list_size * p.n_iter t0 = time.time() + i = 0 def process_cell(x, y, z, ix, iy, iz): - nonlocal processed_result + nonlocal processed_result, i + i += 1 + shared.log.debug(f'XYZ grid process: x={ix+1}/{len(xs)} y={iy+1}/{len(ys)} z={iz+1}/{len(zs)} total={i/list_size:.2f}') def index(ix, iy, iz): return ix + iy * len(xs) + iz * len(xs) * len(ys) shared.state.job = 'grid' + p0 = time.time() processed: processing.Processed = cell(x, y, z, ix, iy, iz) + p1 = time.time() if processed_result is None: processed_result = copy(processed) if processed_result is None: @@ -30,13 +35,17 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend processed_result.all_prompts = [None] * list_size processed_result.all_seeds = [None] * list_size processed_result.infotexts = [None] * list_size + processed_result.time = [0] * list_size processed_result.index_of_first_image = 1 idx = index(ix, iy, iz) if processed is not None and processed.images: processed_result.images[idx] = processed.images[0] + if include_time: + processed_result.images[idx] = images.draw_overlay(processed_result.images[idx], f'time: {p1 - p0:.2f}') processed_result.all_prompts[idx] = processed.prompt processed_result.all_seeds[idx] = processed.seed processed_result.infotexts[idx] = processed.infotexts[0] + processed_result.time[idx] = round(p1 - p0, 2) else: cell_mode = "P" cell_size = (processed_result.width, processed_result.height) @@ -44,6 +53,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend cell_mode = processed_result.images[0].mode cell_size = processed_result.images[0].size processed_result.images[idx] = Image.new(cell_mode, cell_size) + return if first_axes_processed == 'x': for ix, x in enumerate(xs): @@ -93,7 +103,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if (not no_grid or include_sub_grids) and images.check_grid_size(to_process): grid = images.image_grid(to_process, rows=len(ys)) if draw_legend: - grid = images.draw_grid_annotations(grid, w, h, hor_texts, ver_texts, margin_size, title=title_texts[i]) + grid = images.draw_grid_annotations(grid, w, h, x_texts, y_texts, margin_size, title=z_texts[i]) processed_result.images.insert(i, grid) processed_result.all_prompts.insert(i, processed_result.all_prompts[idx0]) processed_result.all_seeds.insert(i, processed_result.all_seeds[idx0]) diff --git a/scripts/xyz_grid_on.py b/scripts/xyz_grid_on.py index ac1bc4c2f..3298ee572 100644 --- a/scripts/xyz_grid_on.py +++ b/scripts/xyz_grid_on.py @@ -57,9 +57,10 @@ class Script(scripts.Script): draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"), container=False) csv_mode = gr.Checkbox(label='Use text inputs', value=False, elem_id=self.elem_id("csv_mode"), container=False) no_fixed_seeds = gr.Checkbox(label='Use random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"), container=False) + include_time = gr.Checkbox(label='Add time info', value=False, elem_id=self.elem_id("include_time"), container=False) with gr.Column(): - include_grid = gr.Checkbox(label='Create main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) - include_subgrids = gr.Checkbox(label='Create partial grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_grid = gr.Checkbox(label='Include main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) + include_subgrids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) include_images = gr.Checkbox(label='Include images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) with gr.Row(): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) @@ -139,9 +140,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), ) - return [enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size] + return [enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size] - def process(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size): # pylint: disable=W0221 + def process(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size): # pylint: disable=W0221 global active, cache # pylint: disable=W0603 cache = None if not enabled or active: @@ -327,6 +328,7 @@ class Script(scripts.Script): second_axes_processed=second_axes_processed, margin_size=margin_size, no_grid=not include_grid, + include_time=include_time, ) if not processed.images: @@ -355,5 +357,5 @@ class Script(scripts.Script): cache = processed return processed - def process_images(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size): # pylint: disable=W0221, W0613 + def process_images(self, p, *args): # pylint: disable=W0221, W0613 return cache