diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..ce213ceb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Ensure that shell scripts always use lf line endings, e.g. entrypoint.sh for docker +* text=auto +*.sh text eol=lf \ No newline at end of file diff --git a/css/style.css b/css/style.css index b5f7a448..649f77c5 100644 --- a/css/style.css +++ b/css/style.css @@ -72,7 +72,7 @@ progress::after { .progress-bar span { text-align: right; - width: 200px; + width: 215px; } .type_row{ @@ -399,4 +399,12 @@ progress::after { text-align: center; border-radius: 5px 5px 0px 0px; display: none; /* remove this to enable tooltip in preview image */ +} + +#inpaint_canvas .canvas-tooltip-info { + top: 2px; +} + +#inpaint_brush_color input[type=color]{ + background: none; } \ No newline at end of file diff --git a/fooocus_version.py b/fooocus_version.py index d6d40174..fd8b8907 100644 --- a/fooocus_version.py +++ b/fooocus_version.py @@ -1 +1 @@ -version = '2.4.0-rc4 (mashb1t)' +version = '2.4.0 (mashb1t)' diff --git a/language/en.json b/language/en.json index ca4acecc..bf2e97d0 100644 --- a/language/en.json +++ b/language/en.json @@ -9,8 +9,15 @@ "Advanced": "Advanced", "Upscale or Variation": "Upscale or Variation", "Image Prompt": "Image Prompt", - "Inpaint or Outpaint (beta)": "Inpaint or Outpaint (beta)", - "Drag above image to here": "Drag above image to here", + "Inpaint or Outpaint": "Inpaint or Outpaint", + "Outpaint Direction": "Outpaint Direction", + "Method": "Method", + "Describe": "Describe", + "Content Type": "Content Type", + "Photograph": "Photograph", + "Art/Anime": "Art/Anime", + "Describe this Image into Prompt": "Describe this Image into Prompt", + "Image Size and Recommended Size": "Image Size and Recommended Size", "Upscale or Variation:": "Upscale or Variation:", "Disabled": "Disabled", "Vary (Subtle)": "Vary (Subtle)", @@ -323,6 +330,7 @@ "vae": "vae", "CFG Mimicking from TSNR": "CFG Mimicking from TSNR", "Enabling Fooocus's implementation of CFG mimicking for TSNR (effective when real CFG > mimicked CFG).": "Enabling Fooocus's implementation of CFG mimicking for TSNR (effective when real CFG > mimicked CFG).", + "CLIP Skip": "CLIP Skip", "Sampler": "Sampler", "dpmpp_2m_sde_gpu": "dpmpp_2m_sde_gpu", "Only effective in non-inpaint mode.": "Only effective in non-inpaint mode.", @@ -394,7 +402,7 @@ "Fooocus Enhance": "Fooocus Enhance", "Fooocus Cinematic": "Fooocus Cinematic", "Fooocus Sharp": "Fooocus Sharp", - "Drag any image generated by Fooocus here": "Drag any image generated by Fooocus here", + "For images created by Fooocus": "For images created by Fooocus", "Metadata": "Metadata", "Apply Metadata": "Apply Metadata", "Metadata Scheme": "Metadata Scheme", diff --git a/modules/async_worker.py b/modules/async_worker.py index 0d73d61d..0f620210 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -177,6 +177,7 @@ def worker(): adm_scaler_negative = args.pop() adm_scaler_end = args.pop() adaptive_cfg = args.pop() + clip_skip = args.pop() sampler_name = args.pop() scheduler_name = args.pop() vae_name = args.pop() @@ -305,6 +306,7 @@ def worker(): negative_prompt = translate2en(negative_prompt, 'negative prompt') print(f'[Parameters] Adaptive CFG = {adaptive_cfg}') + print(f'[Parameters] CLIP Skip = {clip_skip}') print(f'[Parameters] Sharpness = {sharpness}') print(f'[Parameters] ControlNet Softness = {controlnet_softness}') print(f'[Parameters] ADM Scale = ' @@ -478,6 +480,8 @@ def worker(): loras=loras, base_model_additional_loras=base_model_additional_loras, use_synthetic_refiner=use_synthetic_refiner, vae_name=vae_name) + pipeline.set_clip_skip(clip_skip) + progressbar(async_task, 3, 'Processing prompts ...') tasks = [] @@ -937,6 +941,8 @@ def worker(): d.append( ('CFG Mimicking from TSNR', 'adaptive_cfg', modules.patch.patch_settings[pid].adaptive_cfg)) + if clip_skip > 1: + d.append(('CLIP Skip', 'clip_skip', clip_skip)) d.append(('Sampler', 'sampler', sampler_name)) d.append(('Scheduler', 'scheduler', scheduler_name)) d.append(('VAE', 'vae', vae_name)) diff --git a/modules/config.py b/modules/config.py index 92bc129a..a4050220 100644 --- a/modules/config.py +++ b/modules/config.py @@ -436,6 +436,11 @@ default_cfg_tsnr = get_config_item_or_set_default( default_value=7.0, validator=lambda x: isinstance(x, numbers.Number) ) +default_clip_skip = get_config_item_or_set_default( + key='default_clip_skip', + default_value=1, + validator=lambda x: isinstance(x, numbers.Number) +) default_overwrite_step = get_config_item_or_set_default( key='default_overwrite_step', default_value=-1, @@ -517,6 +522,8 @@ possible_preset_keys = { "default_loras": "", "default_cfg_scale": "guidance_scale", "default_sample_sharpness": "sharpness", + "default_cfg_tsnr": "adaptive_cfg", + "default_clip_skip": "clip_skip", "default_sampler": "sampler", "default_scheduler": "scheduler", "default_overwrite_step": "steps", diff --git a/modules/default_pipeline.py b/modules/default_pipeline.py index 38f914c5..494644d6 100644 --- a/modules/default_pipeline.py +++ b/modules/default_pipeline.py @@ -201,6 +201,17 @@ def clip_encode(texts, pool_top_k=1): return [[torch.cat(cond_list, dim=1), {"pooled_output": pooled_acc}]] +@torch.no_grad() +@torch.inference_mode() +def set_clip_skip(clip_skip: int): + global final_clip + + if final_clip is None: + return + + final_clip.clip_layer(-abs(clip_skip)) + return + @torch.no_grad() @torch.inference_mode() def clear_all_caches(): diff --git a/modules/meta_parser.py b/modules/meta_parser.py index 261c2f0e..c9680c01 100644 --- a/modules/meta_parser.py +++ b/modules/meta_parser.py @@ -34,16 +34,17 @@ def load_parameter_button_click(raw_metadata: dict | str, is_generating: bool): get_list('styles', 'Styles', loaded_parameter_dict, results) get_str('performance', 'Performance', loaded_parameter_dict, results) get_steps('steps', 'Steps', loaded_parameter_dict, results) - get_float('overwrite_switch', 'Overwrite Switch', loaded_parameter_dict, results) + get_number('overwrite_switch', 'Overwrite Switch', loaded_parameter_dict, results) get_resolution('resolution', 'Resolution', loaded_parameter_dict, results) - get_float('guidance_scale', 'Guidance Scale', loaded_parameter_dict, results) - get_float('sharpness', 'Sharpness', loaded_parameter_dict, results) + get_number('guidance_scale', 'Guidance Scale', loaded_parameter_dict, results) + get_number('sharpness', 'Sharpness', loaded_parameter_dict, results) get_adm_guidance('adm_guidance', 'ADM Guidance', loaded_parameter_dict, results) get_str('refiner_swap_method', 'Refiner Swap Method', loaded_parameter_dict, results) - get_float('adaptive_cfg', 'CFG Mimicking from TSNR', loaded_parameter_dict, results) + get_number('adaptive_cfg', 'CFG Mimicking from TSNR', loaded_parameter_dict, results) + get_number('clip_skip', 'CLIP Skip', loaded_parameter_dict, results, cast_type=int) get_str('base_model', 'Base Model', loaded_parameter_dict, results) get_str('refiner_model', 'Refiner Model', loaded_parameter_dict, results) - get_float('refiner_switch', 'Refiner Switch', loaded_parameter_dict, results) + get_number('refiner_switch', 'Refiner Switch', loaded_parameter_dict, results) get_str('sampler', 'Sampler', loaded_parameter_dict, results) get_str('scheduler', 'Scheduler', loaded_parameter_dict, results) get_str('vae', 'VAE', loaded_parameter_dict, results) @@ -83,11 +84,11 @@ def get_list(key: str, fallback: str | None, source_dict: dict, results: list, d results.append(gr.update()) -def get_float(key: str, fallback: str | None, source_dict: dict, results: list, default=None): +def get_number(key: str, fallback: str | None, source_dict: dict, results: list, default=None, cast_type=float): try: h = source_dict.get(key, source_dict.get(fallback, default)) assert h is not None - h = float(h) + h = cast_type(h) results.append(h) except: results.append(gr.update()) @@ -313,6 +314,7 @@ class A1111MetadataParser(MetadataParser): 'adm_guidance': 'ADM Guidance', 'refiner_swap_method': 'Refiner Swap Method', 'adaptive_cfg': 'Adaptive CFG', + 'clip_skip': 'Clip skip', 'overwrite_switch': 'Overwrite Switch', 'freeu': 'FreeU', 'base_model': 'Model', @@ -457,7 +459,7 @@ class A1111MetadataParser(MetadataParser): self.fooocus_to_a1111['refiner_model_hash']: self.refiner_model_hash } - for key in ['adaptive_cfg', 'overwrite_switch', 'refiner_swap_method', 'freeu']: + for key in ['adaptive_cfg', 'clip_skip', 'overwrite_switch', 'refiner_swap_method', 'freeu']: if key in data: generation_params[self.fooocus_to_a1111[key]] = data[key] diff --git a/modules/util.py b/modules/util.py index fba6082f..eb2c6351 100644 --- a/modules/util.py +++ b/modules/util.py @@ -484,7 +484,8 @@ def get_image_size_info(image: np.ndarray, aspect_ratios: list) -> str: recommended_gcd = math.gcd(recommended_width, recommended_height) recommended_lcm_ratio = f'{recommended_width // recommended_gcd}:{recommended_height // recommended_gcd}' - size_info += f'\nRecommended Size: {recommended_width} x {recommended_height}, Ratio: {recommended_ratio}, {recommended_lcm_ratio}' + size_info = f'{width} x {height}, {ratio}, {lcm_ratio}' + size_info += f'\n{recommended_width} x {recommended_height}, {recommended_ratio}, {recommended_lcm_ratio}' return size_info except Exception as e: diff --git a/tests/test_utils.py b/tests/test_utils.py index e7eb991c..6fd550db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -77,6 +77,5 @@ class TestUtils(unittest.TestCase): for test in test_cases: prompt, loras, loras_limit, skip_file_check = test["input"] expected = test["output"] - actual = util.parse_lora_references_from_prompt(prompt, loras, loras_limit=loras_limit, - skip_file_check=skip_file_check) + actual = util.parse_lora_references_from_prompt(prompt, loras, loras_limit=loras_limit, skip_file_check=skip_file_check) self.assertEqual(expected, actual) diff --git a/update_log.md b/update_log.md index 62c4882b..e9544da3 100644 --- a/update_log.md +++ b/update_log.md @@ -1,3 +1,22 @@ +# [2.4.0](https://github.com/lllyasviel/Fooocus/releases/tag/v2.4.0) + +* Add clip skip slider +* Add select for custom VAE +* Add new style "Random Style" +* Update default anime model to animaPencilXL_v310 +* Add button to reconnect the UI after Fooocus crashed without having to configure everything again (no page reload required) +* Add performance "hyper-sd" (based on [Hyper-SDXL 4 step LoRA](https://huggingface.co/ByteDance/Hyper-SD/blob/main/Hyper-SDXL-4steps-lora.safetensors)) +* Add [AlignYourSteps](https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/) scheduler by Nvidia, see +* Add [TCD](https://github.com/jabir-zheng/TCD) sampler and scheduler (based on sgm_uniform) +* Add NSFW image censoring (disables intermediate image preview while generating). Set config value `default_black_out_nsfw` to True to always enable. +* Add argument `--enable-describe-uov-image` to automatically describe uploaded images for upscaling +* Add inline lora prompt references with subfolder support, example prompt: `colorful bird ` +* Add size and aspect ratio recommendation on image describe +* Add inpaint brush color picker, helpful when image and mask brush have the same color +* Add automated Docker image build using Github Actions on each release. +* Add full raw prompts to history logs +* Change code ownership from @lllyasviel to @mashb1t for automated issue / MR notification + # [2.3.1](https://github.com/lllyasviel/Fooocus/releases/tag/2.3.1) * Remove positive prompt from anime prefix to not reset prompt after switching presets diff --git a/webui.py b/webui.py index 47982365..1ba22f83 100644 --- a/webui.py +++ b/webui.py @@ -152,7 +152,7 @@ with shared.gradio_root: with gr.TabItem(label='Upscale or Variation') as uov_tab: with gr.Row(): with gr.Column(): - uov_input_image = grh.Image(label='Drag above image to here', source='upload', type='numpy') + uov_input_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False) with gr.Column(): uov_method = gr.Radio(label='Upscale or Variation:', choices=flags.uov_list, value=flags.disabled) gr.HTML('\U0001F4D4 Document') @@ -203,7 +203,7 @@ with shared.gradio_root: with gr.TabItem(label='Inpaint or Outpaint') as inpaint_tab: with gr.Row(): with gr.Column(): - inpaint_input_image = grh.Image(label='Drag inpaint or outpaint image to here', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", elem_id='inpaint_canvas') + inpaint_input_image = grh.Image(label='Image', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", elem_id='inpaint_canvas', show_label=False) inpaint_mode = gr.Dropdown(choices=modules.flags.inpaint_options, value=modules.flags.inpaint_option_default, label='Method') inpaint_additional_prompt = gr.Textbox(placeholder="Describe what you want to inpaint.", elem_id='inpaint_additional_prompt', label='Inpaint Additional Prompt', visible=False) outpaint_selections = gr.CheckboxGroup(choices=['Left', 'Right', 'Top', 'Bottom'], value=[], label='Outpaint Direction') @@ -266,14 +266,14 @@ with shared.gradio_root: with gr.TabItem(label='Describe') as desc_tab: with gr.Row(): with gr.Column(): - desc_input_image = grh.Image(label='Drag any image to here', source='upload', type='numpy') + desc_input_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False) with gr.Column(): desc_method = gr.Radio( label='Content Type', choices=[flags.desc_type_photo, flags.desc_type_anime], value=flags.desc_type_photo) desc_btn = gr.Button(value='Describe this Image into Prompt') - desc_image_size = gr.Markdown(label='Image Size', elem_id='desc_image_size', visible=False) + desc_image_size = gr.Textbox(label='Image Size and Recommended Size', elem_id='desc_image_size', visible=False) gr.HTML('\U0001F4D4 Document') def trigger_show_image_properties(image): @@ -285,7 +285,7 @@ with shared.gradio_root: with gr.TabItem(label='Metadata') as load_tab: with gr.Column(): - metadata_input_image = grh.Image(label='Drag any image generated by Fooocus here', source='upload', type='filepath') + metadata_input_image = grh.Image(label='For images created by Fooocus', source='upload', type='filepath') metadata_json = gr.JSON(label='Metadata') metadata_import_button = gr.Button(value='Apply Metadata') @@ -468,6 +468,9 @@ with shared.gradio_root: value=modules.config.default_cfg_tsnr, info='Enabling Fooocus\'s implementation of CFG mimicking for TSNR ' '(effective when real CFG > mimicked CFG).') + clip_skip = gr.Slider(label='CLIP Skip', minimum=1, maximum=10, step=1, + value=modules.config.default_clip_skip, + info='Bypass CLIP layers to avoid overfitting (use 1 to disable).') sampler_name = gr.Dropdown(label='Sampler', choices=flags.sampler_list, value=modules.config.default_sampler) scheduler_name = gr.Dropdown(label='Scheduler', choices=flags.scheduler_list, @@ -582,6 +585,8 @@ with shared.gradio_root: inpaint_mask_upload_checkbox = gr.Checkbox(label='Enable Mask Upload', value=False) invert_mask_checkbox = gr.Checkbox(label='Invert Mask', value=False) + inpaint_mask_color = gr.ColorPicker(label='Inpaint brush color', value='#FFFFFF', elem_id='inpaint_brush_color') + inpaint_ctrls = [debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine, inpaint_strength, inpaint_respective_field, inpaint_mask_upload_checkbox, invert_mask_checkbox, inpaint_erode_or_dilate] @@ -591,6 +596,10 @@ with shared.gradio_root: outputs=[inpaint_mask_image, inpaint_mask_generation_col], queue=False, show_progress=False) + inpaint_mask_color.change(lambda x: gr.update(brush_color=x), inputs=inpaint_mask_color, + outputs=inpaint_input_image, + queue=False, show_progress=False) + with gr.Tab(label='FreeU'): freeu_enabled = gr.Checkbox(label='Enabled', value=False) freeu_b1 = gr.Slider(label='B1', minimum=0, maximum=2, step=0.01, value=1.01) @@ -646,9 +655,9 @@ with shared.gradio_root: load_data_outputs = [advanced_checkbox, image_number, prompt, negative_prompt, style_selections, performance_selection, overwrite_step, overwrite_switch, aspect_ratios_selection, overwrite_width, overwrite_height, guidance_scale, sharpness, adm_scaler_positive, - adm_scaler_negative, adm_scaler_end, refiner_swap_method, adaptive_cfg, base_model, - refiner_model, refiner_switch, sampler_name, scheduler_name, vae_name, seed_random, - image_seed, generate_button, load_parameter_button] + freeu_ctrls + lora_ctrls + adm_scaler_negative, adm_scaler_end, refiner_swap_method, adaptive_cfg, clip_skip, + base_model, refiner_model, refiner_switch, sampler_name, scheduler_name, vae_name, + seed_random, image_seed, generate_button, load_parameter_button] + freeu_ctrls + lora_ctrls if not args_manager.args.disable_preset_selection: def preset_selection_change(preset, is_generating): @@ -734,7 +743,7 @@ with shared.gradio_root: ctrls += [uov_method, uov_input_image] ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] ctrls += [disable_preview, disable_intermediate_results, disable_seed_increment, black_out_nsfw] - ctrls += [adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg] + ctrls += [adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg, clip_skip] ctrls += [sampler_name, scheduler_name, vae_name] ctrls += [overwrite_step, overwrite_switch, overwrite_width, overwrite_height, overwrite_vary_strength] ctrls += [overwrite_upscale_strength, mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint]