From 7537612bcc43cf76a5caf9901e6fcf37099d554e Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Mon, 20 May 2024 19:21:41 +0200 Subject: [PATCH 01/10] feat: only use valid inline loras, add subfolder support (#2968) --- modules/config.py | 14 +++++++++++++- modules/meta_parser.py | 21 ++++----------------- modules/util.py | 41 +++++++++++++++++++++++++++++------------ tests/test_utils.py | 32 +++++++++++++++++++++++++------- 4 files changed, 71 insertions(+), 37 deletions(-) diff --git a/modules/config.py b/modules/config.py index 913fb281..94046661 100644 --- a/modules/config.py +++ b/modules/config.py @@ -547,6 +547,7 @@ with open(config_example_path, "w", encoding="utf-8") as json_file: model_filenames = [] lora_filenames = [] +lora_filenames_no_special = [] vae_filenames = [] wildcard_filenames = [] @@ -556,6 +557,16 @@ sdxl_hyper_sd_lora = 'sdxl_hyper_sd_4step_lora.safetensors' loras_metadata_remove = [sdxl_lcm_lora, sdxl_lightning_lora, sdxl_hyper_sd_lora] +def remove_special_loras(lora_filenames): + global loras_metadata_remove + + loras_no_special = lora_filenames.copy() + for lora_to_remove in loras_metadata_remove: + if lora_to_remove in loras_no_special: + loras_no_special.remove(lora_to_remove) + return loras_no_special + + def get_model_filenames(folder_paths, extensions=None, name_filter=None): if extensions is None: extensions = ['.pth', '.ckpt', '.bin', '.safetensors', '.fooocus.patch'] @@ -570,9 +581,10 @@ def get_model_filenames(folder_paths, extensions=None, name_filter=None): def update_files(): - global model_filenames, lora_filenames, vae_filenames, wildcard_filenames, available_presets + global model_filenames, lora_filenames, lora_filenames_no_special, vae_filenames, wildcard_filenames, available_presets model_filenames = get_model_filenames(paths_checkpoints) lora_filenames = get_model_filenames(paths_loras) + lora_filenames_no_special = remove_special_loras(lora_filenames) vae_filenames = get_model_filenames(path_vae) wildcard_filenames = get_files_from_folder(path_wildcards, ['.txt']) available_presets = get_presets() diff --git a/modules/meta_parser.py b/modules/meta_parser.py index 84032e82..2469da5f 100644 --- a/modules/meta_parser.py +++ b/modules/meta_parser.py @@ -205,7 +205,6 @@ def get_lora(key: str, fallback: str | None, source_dict: dict, results: list): def get_sha256(filepath): global hash_cache if filepath not in hash_cache: - # is_safetensors = os.path.splitext(filepath)[1].lower() == '.safetensors' hash_cache[filepath] = sha256(filepath) return hash_cache[filepath] @@ -293,12 +292,6 @@ class MetadataParser(ABC): self.loras.append((Path(lora_name).stem, lora_weight, lora_hash)) self.vae_name = Path(vae_name).stem - @staticmethod - def remove_special_loras(lora_filenames): - for lora_to_remove in modules.config.loras_metadata_remove: - if lora_to_remove in lora_filenames: - lora_filenames.remove(lora_to_remove) - class A1111MetadataParser(MetadataParser): def get_scheme(self) -> MetadataScheme: @@ -415,13 +408,11 @@ class A1111MetadataParser(MetadataParser): lora_data = data['lora_hashes'] if lora_data != '': - lora_filenames = modules.config.lora_filenames.copy() - self.remove_special_loras(lora_filenames) for li, lora in enumerate(lora_data.split(', ')): lora_split = lora.split(': ') lora_name = lora_split[0] lora_weight = lora_split[2] if len(lora_split) == 3 else lora_split[1] - for filename in lora_filenames: + for filename in modules.config.lora_filenames_no_special: path = Path(filename) if lora_name == path.stem: data[f'lora_combined_{li + 1}'] = f'{filename} : {lora_weight}' @@ -510,19 +501,15 @@ class FooocusMetadataParser(MetadataParser): return MetadataScheme.FOOOCUS def parse_json(self, metadata: dict) -> dict: - model_filenames = modules.config.model_filenames.copy() - lora_filenames = modules.config.lora_filenames.copy() - vae_filenames = modules.config.vae_filenames.copy() - self.remove_special_loras(lora_filenames) for key, value in metadata.items(): if value in ['', 'None']: continue if key in ['base_model', 'refiner_model']: - metadata[key] = self.replace_value_with_filename(key, value, model_filenames) + metadata[key] = self.replace_value_with_filename(key, value, modules.config.model_filenames) elif key.startswith('lora_combined_'): - metadata[key] = self.replace_value_with_filename(key, value, lora_filenames) + metadata[key] = self.replace_value_with_filename(key, value, modules.config.lora_filenames_no_special) elif key == 'vae': - metadata[key] = self.replace_value_with_filename(key, value, vae_filenames) + metadata[key] = self.replace_value_with_filename(key, value, modules.config.vae_filenames) else: continue diff --git a/modules/util.py b/modules/util.py index 52bc490a..cb5580fb 100644 --- a/modules/util.py +++ b/modules/util.py @@ -1,3 +1,5 @@ +from pathlib import Path + import numpy as np import datetime import random @@ -360,6 +362,14 @@ def is_json(data: str) -> bool: return True +def get_filname_by_stem(lora_name, filenames: List[str]) -> str | None: + for filename in filenames: + path = Path(filename) + if lora_name == path.stem: + return filename + return None + + def get_file_from_folder_list(name, folders): if not isinstance(folders, list): folders = [folders] @@ -377,28 +387,35 @@ def get_enabled_loras(loras: list, remove_none=True) -> list: def parse_lora_references_from_prompt(prompt: str, loras: List[Tuple[AnyStr, float]], loras_limit: int = 5, - prompt_cleanup=True, deduplicate_loras=True) -> tuple[List[Tuple[AnyStr, float]], str]: + skip_file_check=False, prompt_cleanup=True, deduplicate_loras=True) -> tuple[List[Tuple[AnyStr, float]], str]: found_loras = [] - prompt_without_loras = "" - for token in prompt.split(" "): + prompt_without_loras = '' + cleaned_prompt = '' + for token in prompt.split(','): matches = LORAS_PROMPT_PATTERN.findall(token) - if matches: - for match in matches: - found_loras.append((f"{match[1]}.safetensors", float(match[2]))) - prompt_without_loras += token.replace(match[0], '') - else: - prompt_without_loras += token - prompt_without_loras += ' ' + if len(matches) == 0: + prompt_without_loras += token + ', ' + continue + for match in matches: + lora_name = match[1] + '.safetensors' + if not skip_file_check: + lora_name = get_filname_by_stem(match[1], modules.config.lora_filenames_no_special) + if lora_name is not None: + found_loras.append((lora_name, float(match[2]))) + token = token.replace(match[0], '') + prompt_without_loras += token + ', ' + + if prompt_without_loras != '': + cleaned_prompt = prompt_without_loras[:-2] - cleaned_prompt = prompt_without_loras[:-1] if prompt_cleanup: cleaned_prompt = cleanup_prompt(prompt_without_loras) new_loras = [] lora_names = [lora[0] for lora in loras] for found_lora in found_loras: - if deduplicate_loras and found_lora[0] in lora_names: + if deduplicate_loras and (found_lora[0] in lora_names or found_lora in new_loras): continue new_loras.append(found_lora) diff --git a/tests/test_utils.py b/tests/test_utils.py index 9f81005b..6fd550db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,13 +7,13 @@ class TestUtils(unittest.TestCase): def test_can_parse_tokens_with_lora(self): test_cases = [ { - "input": ("some prompt, very cool, , cool ", [], 5), + "input": ("some prompt, very cool, , cool ", [], 5, True), "output": ( [('hey-lora.safetensors', 0.4), ('you-lora.safetensors', 0.2)], 'some prompt, very cool, cool'), }, # Test can not exceed limit { - "input": ("some prompt, very cool, , cool ", [], 1), + "input": ("some prompt, very cool, , cool ", [], 1, True), "output": ( [('hey-lora.safetensors', 0.4)], 'some prompt, very cool, cool' @@ -25,6 +25,7 @@ class TestUtils(unittest.TestCase): "some prompt, very cool, , , , , , ", [("hey-lora.safetensors", 0.4)], 5, + True ), "output": ( [ @@ -37,18 +38,35 @@ class TestUtils(unittest.TestCase): 'some prompt, very cool' ) }, + # test correct matching even if there is no space separating loras in the same token { - "input": ("some prompt, very cool, ", [], 3), + "input": ("some prompt, very cool, ", [], 3, True), "output": ( [ ('hey-lora.safetensors', 0.4), ('you-lora.safetensors', 0.2) ], - 'some prompt, very cool, ' + 'some prompt, very cool' + ), + }, + # test deduplication, also selected loras are never overridden with loras in prompt + { + "input": ( + "some prompt, very cool, ", + [('you-lora.safetensors', 0.3)], + 3, + True + ), + "output": ( + [ + ('you-lora.safetensors', 0.3), + ('hey-lora.safetensors', 0.4) + ], + 'some prompt, very cool' ), }, { - "input": (", , , and ", [], 6), + "input": (", , , and ", [], 6, True), "output": ( [], ', , , and ' @@ -57,7 +75,7 @@ class TestUtils(unittest.TestCase): ] for test in test_cases: - prompt, loras, loras_limit = test["input"] + prompt, loras, loras_limit, skip_file_check = test["input"] expected = test["output"] - actual = util.parse_lora_references_from_prompt(prompt, loras, loras_limit) + actual = util.parse_lora_references_from_prompt(prompt, loras, loras_limit=loras_limit, skip_file_check=skip_file_check) self.assertEqual(expected, actual) From 302bfdf855ca8271d5ddc87a6db89504fb519718 Mon Sep 17 00:00:00 2001 From: xhoxye <129571231+xhoxye@users.noreply.github.com> Date: Thu, 23 May 2024 02:47:44 +0800 Subject: [PATCH 02/10] feat: read size and ratio of an image and provide the recommended size (#2971) * Add the information about the size and ratio of the read image * feat: use available aspect ratios from config, move function to util, change default visibility of label * refactor: extract sdxl aspect ratios to flags, use in describe as discussed in https://github.com/lllyasviel/Fooocus/pull/2971#discussion_r1608493765 https://github.com/lllyasviel/Fooocus/pull/2971#issuecomment-2123620595 --------- Co-authored-by: Manuel Schmid Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> --- modules/config.py | 10 ++-------- modules/flags.py | 7 +++++++ modules/meta_parser.py | 2 +- modules/util.py | 32 ++++++++++++++++++++++++++++++++ webui.py | 11 ++++++++++- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/modules/config.py b/modules/config.py index 94046661..64b0b86f 100644 --- a/modules/config.py +++ b/modules/config.py @@ -416,13 +416,7 @@ embeddings_downloads = get_config_item_or_set_default( ) available_aspect_ratios = get_config_item_or_set_default( key='available_aspect_ratios', - default_value=[ - '704*1408', '704*1344', '768*1344', '768*1280', '832*1216', '832*1152', - '896*1152', '896*1088', '960*1088', '960*1024', '1024*1024', '1024*960', - '1088*960', '1088*896', '1152*896', '1152*832', '1216*832', '1280*768', - '1344*768', '1344*704', '1408*704', '1472*704', '1536*640', '1600*640', - '1664*576', '1728*576' - ], + default_value=modules.flags.sdxl_aspect_ratios, validator=lambda x: isinstance(x, list) and all('*' in v for v in x) and len(x) > 1 ) default_aspect_ratio = get_config_item_or_set_default( @@ -526,7 +520,7 @@ def add_ratio(x): default_aspect_ratio = add_ratio(default_aspect_ratio) -available_aspect_ratios = [add_ratio(x) for x in available_aspect_ratios] +available_aspect_ratios_labels = [add_ratio(x) for x in available_aspect_ratios] # Only write config in the first launch. diff --git a/modules/flags.py b/modules/flags.py index 7b3ac393..89e1ea0f 100644 --- a/modules/flags.py +++ b/modules/flags.py @@ -81,6 +81,13 @@ inpaint_options = [inpaint_option_default, inpaint_option_detail, inpaint_option desc_type_photo = 'Photograph' desc_type_anime = 'Art/Anime' +sdxl_aspect_ratios = [ + '704*1408', '704*1344', '768*1344', '768*1280', '832*1216', '832*1152', + '896*1152', '896*1088', '960*1088', '960*1024', '1024*1024', '1024*960', + '1088*960', '1088*896', '1152*896', '1152*832', '1216*832', '1280*768', + '1344*768', '1344*704', '1408*704', '1472*704', '1536*640', '1600*640', + '1664*576', '1728*576' +] class MetadataScheme(Enum): FOOOCUS = 'fooocus' diff --git a/modules/meta_parser.py b/modules/meta_parser.py index 2469da5f..4ce12435 100644 --- a/modules/meta_parser.py +++ b/modules/meta_parser.py @@ -124,7 +124,7 @@ def get_resolution(key: str, fallback: str | None, source_dict: dict, results: l h = source_dict.get(key, source_dict.get(fallback, default)) width, height = eval(h) formatted = modules.config.add_ratio(f'{width}*{height}') - if formatted in modules.config.available_aspect_ratios: + if formatted in modules.config.available_aspect_ratios_labels: results.append(formatted) results.append(-1) results.append(-1) diff --git a/modules/util.py b/modules/util.py index cb5580fb..4f975bf5 100644 --- a/modules/util.py +++ b/modules/util.py @@ -381,6 +381,16 @@ def get_file_from_folder_list(name, folders): return os.path.abspath(os.path.realpath(os.path.join(folders[0], name))) +def ordinal_suffix(number: int) -> str: + return 'th' if 10 <= number % 100 <= 20 else {1: 'st', 2: 'nd', 3: 'rd'}.get(number % 10, 'th') + + +def makedirs_with_log(path): + try: + os.makedirs(path, exist_ok=True) + except OSError as error: + print(f'Directory {path} could not be created, reason: {error}') + def get_enabled_loras(loras: list, remove_none=True) -> list: return [(lora[1], lora[2]) for lora in loras if lora[0] and (lora[1] != 'None' if remove_none else True)] @@ -467,3 +477,25 @@ def apply_wildcards(wildcard_text, rng, i, read_wildcards_in_order) -> str: print(f'[Wildcards] BFS stack overflow. Current text: {wildcard_text}') return wildcard_text + + +def get_image_size_info(image: np.ndarray, aspect_ratios: list) -> str: + try: + image = Image.fromarray(np.uint8(image)) + width, height = image.size + ratio = round(width / height, 2) + gcd = math.gcd(width, height) + lcm_ratio = f'{width // gcd}:{height // gcd}' + size_info = f'Image Size: {width} x {height}, Ratio: {ratio}, {lcm_ratio}' + + closest_ratio = min(aspect_ratios, key=lambda x: abs(ratio - float(x.split('*')[0]) / float(x.split('*')[1]))) + recommended_width, recommended_height = map(int, closest_ratio.split('*')) + recommended_ratio = round(recommended_width / recommended_height, 2) + 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}' + + return size_info + except Exception as e: + return f'Error reading image: {e}' diff --git a/webui.py b/webui.py index 55f3102c..7606e010 100644 --- a/webui.py +++ b/webui.py @@ -221,7 +221,16 @@ with shared.gradio_root: 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) gr.HTML('\U0001F4D4 Document') + + def trigger_show_image_properties(image): + value = modules.util.get_image_size_info(image, modules.flags.sdxl_aspect_ratios) + return gr.update(value=value, visible=True) + + desc_input_image.upload(trigger_show_image_properties, inputs=desc_input_image, + outputs=desc_image_size, show_progress=False, queue=False) + 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') @@ -266,7 +275,7 @@ with shared.gradio_root: performance_selection = gr.Radio(label='Performance', choices=flags.Performance.list(), value=modules.config.default_performance) - aspect_ratios_selection = gr.Radio(label='Aspect Ratios', choices=modules.config.available_aspect_ratios, + aspect_ratios_selection = gr.Radio(label='Aspect Ratios', choices=modules.config.available_aspect_ratios_labels, value=modules.config.default_aspect_ratio, info='width × height', elem_classes='aspect_ratios') image_number = gr.Slider(label='Image Number', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number) From 4da5a68c1015496c23c59d23d41e81e443ce1603 Mon Sep 17 00:00:00 2001 From: xyny <60004820+xynydev@users.noreply.github.com> Date: Wed, 22 May 2024 22:19:54 +0000 Subject: [PATCH 03/10] feat: build and push container image for ghcr.io, update docker.md, and other related fixes (#2805) * chore: update cuda version in container * fix: use symlink to fix error libcuda.so: cannot open shared object file: * fix: update docker entrypoint to use entry_with_update.py * feat: add container build & push workflow * fix: container action run conditions * fix: container action versions * fix: container action versions v2 * fix: docker action registry login and metadata * docs: adjust docker documentation based on latest changes, add docs for podman and docker * chore: replace image name env var with github.event.repository.name Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * chore: replace image name env var with github.event.repository.name (pt2) Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * fix: switch to semver versioning Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * fix: build only on versioned tags Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * fix: don't update in entrypoint Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * fix: remove dash in "docker-compose" Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> * feat: sync pytorch for docker with version used in prepare_environment * feat: update cuda to 12.4.1 * fix: correctly clone checked out version in builds, not always main * refactor: remove irrelevant version in docker-compose.yml --------- Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Co-authored-by: Manuel Schmid --- .dockerignore | 55 +++++++++++++++++- .github/dependabot.yml | 6 ++ .github/workflows/build_container.yml | 44 ++++++++++++++ Dockerfile | 4 +- docker-compose.yml | 4 +- docker.md | 82 ++++++++++++++++++++++++--- requirements_docker.txt | 7 +-- 7 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build_container.yml diff --git a/.dockerignore b/.dockerignore index 485dee64..d1eab807 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,54 @@ -.idea +__pycache__ +*.ckpt +*.safetensors +*.pth +*.pt +*.bin +*.patch +*.backup +*.corrupted +*.partial +*.onnx +sorted_styles.json +/input +/cache +/language/default.json +/test_imgs +config.txt +config_modification_tutorial.txt +user_path_config.txt +user_path_config-deprecated.txt +/modules/*.png +/repositories +/fooocus_env +/venv +/tmp +/ui-config.json +/outputs +/config.json +/log +/webui.settings.bat +/embeddings +/styles.csv +/params.txt +/styles.csv.bak +/webui-user.bat +/webui-user.sh +/interrogate +/user.css +/.idea +/notification.ogg +/notification.mp3 +/SwinIR +/textual_inversion +.vscode +/extensions +/test/stdout.txt +/test/stderr.txt +/cache.json* +/config_states/ +/node_modules +/package-lock.json +/.coverage* +/auth.json +.DS_Store \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..adee0ed1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" \ No newline at end of file diff --git a/.github/workflows/build_container.yml b/.github/workflows/build_container.yml new file mode 100644 index 00000000..1e118a1f --- /dev/null +++ b/.github/workflows/build_container.yml @@ -0,0 +1,44 @@ +name: Create and publish a container image + +on: + push: + tags: + - 'v*' + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b969cd0e..1172c795 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nvidia/cuda:12.3.1-base-ubuntu22.04 +FROM nvidia/cuda:12.4.1-base-ubuntu22.04 ENV DEBIAN_FRONTEND noninteractive ENV CMDARGS --listen @@ -23,7 +23,7 @@ RUN chown -R user:user /content WORKDIR /content USER user -RUN git clone https://github.com/lllyasviel/Fooocus /content/app +COPY . /content/app RUN mv /content/app/models /content/app/models.org CMD [ "sh", "-c", "/content/entrypoint.sh ${CMDARGS}" ] diff --git a/docker-compose.yml b/docker-compose.yml index dee7b3e7..f724964d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,10 @@ -version: '3.9' - volumes: fooocus-data: services: app: build: . - image: fooocus + image: ghcr.io/lllyasviel/fooocus ports: - "7865:7865" environment: diff --git a/docker.md b/docker.md index 1939d6fc..cd75d9f5 100644 --- a/docker.md +++ b/docker.md @@ -1,35 +1,99 @@ # Fooocus on Docker -The docker image is based on NVIDIA CUDA 12.3 and PyTorch 2.0, see [Dockerfile](Dockerfile) and [requirements_docker.txt](requirements_docker.txt) for details. +The docker image is based on NVIDIA CUDA 12.4 and PyTorch 2.1, see [Dockerfile](Dockerfile) and [requirements_docker.txt](requirements_docker.txt) for details. + +## Requirements + +- A computer with specs good enough to run Fooocus, and proprietary Nvidia drivers +- Docker, Docker Compose, or Podman ## Quick start -**This is just an easy way for testing. Please find more information in the [notes](#notes).** +**More information in the [notes](#notes).** + +### Running with Docker Compose 1. Clone this repository -2. Build the image with `docker compose build` -3. Run the docker container with `docker compose up`. Building the image takes some time. +2. Run the docker container with `docker compose up`. + +### Running with Docker + +```sh +docker run -p 7865:7865 -v fooocus-data:/content/data -it \ +--gpus all \ +-e CMDARGS=--listen \ +-e DATADIR=/content/data \ +-e config_path=/content/data/config.txt \ +-e config_example_path=/content/data/config_modification_tutorial.txt \ +-e path_checkpoints=/content/data/models/checkpoints/ \ +-e path_loras=/content/data/models/loras/ \ +-e path_embeddings=/content/data/models/embeddings/ \ +-e path_vae_approx=/content/data/models/vae_approx/ \ +-e path_upscale_models=/content/data/models/upscale_models/ \ +-e path_inpaint=/content/data/models/inpaint/ \ +-e path_controlnet=/content/data/models/controlnet/ \ +-e path_clip_vision=/content/data/models/clip_vision/ \ +-e path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/ \ +-e path_outputs=/content/app/outputs/ \ +ghcr.io/lllyasviel/fooocus +``` +### Running with Podman + +```sh +podman run -p 7865:7865 -v fooocus-data:/content/data -it \ +--security-opt=no-new-privileges --cap-drop=ALL --security-opt label=type:nvidia_container_t --device=nvidia.com/gpu=all \ +-e CMDARGS=--listen \ +-e DATADIR=/content/data \ +-e config_path=/content/data/config.txt \ +-e config_example_path=/content/data/config_modification_tutorial.txt \ +-e path_checkpoints=/content/data/models/checkpoints/ \ +-e path_loras=/content/data/models/loras/ \ +-e path_embeddings=/content/data/models/embeddings/ \ +-e path_vae_approx=/content/data/models/vae_approx/ \ +-e path_upscale_models=/content/data/models/upscale_models/ \ +-e path_inpaint=/content/data/models/inpaint/ \ +-e path_controlnet=/content/data/models/controlnet/ \ +-e path_clip_vision=/content/data/models/clip_vision/ \ +-e path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/ \ +-e path_outputs=/content/app/outputs/ \ +ghcr.io/lllyasviel/fooocus +``` When you see the message `Use the app with http://0.0.0.0:7865/` in the console, you can access the URL in your browser. -Your models and outputs are stored in the `fooocus-data` volume, which, depending on OS, is stored in `/var/lib/docker/volumes`. +Your models and outputs are stored in the `fooocus-data` volume, which, depending on OS, is stored in `/var/lib/docker/volumes/` (or `~/.local/share/containers/storage/volumes/` when using `podman`). + +## Building the container locally + +Clone the repository first, and open a terminal in the folder. + +Build with `docker`: +```sh +docker build . -t fooocus +``` + +Build with `podman`: +```sh +podman build . -t fooocus +``` ## Details -### Update the container manually +### Update the container manually (`docker compose`) When you are using `docker compose up` continuously, the container is not updated to the latest version of Fooocus automatically. Run `git pull` before executing `docker compose build --no-cache` to build an image with the latest Fooocus version. You can then start it with `docker compose up` ### Import models, outputs -If you want to import files from models or the outputs folder, you can uncomment the following settings in the [docker-compose.yml](docker-compose.yml): + +If you want to import files from models or the outputs folder, you can add the following bind mounts in the [docker-compose.yml](docker-compose.yml) or your preferred method of running the container: ``` #- ./models:/import/models # Once you import files, you don't need to mount again. #- ./outputs:/import/outputs # Once you import files, you don't need to mount again. ``` -After running `docker compose up`, your files will be copied into `/content/data/models` and `/content/data/outputs` -Since `/content/data` is a persistent volume folder, your files will be persisted even when you re-run `docker compose up --build` without above volume settings. +After running the container, your files will be copied into `/content/data/models` and `/content/data/outputs` +Since `/content/data` is a persistent volume folder, your files will be persisted even when you re-run the container without the above mounts. ### Paths inside the container diff --git a/requirements_docker.txt b/requirements_docker.txt index 3cf4aa89..21883adf 100644 --- a/requirements_docker.txt +++ b/requirements_docker.txt @@ -1,5 +1,2 @@ -torch==2.0.1 -torchvision==0.15.2 -torchaudio==2.0.2 -torchtext==0.15.2 -torchdata==0.6.1 +torch==2.1.0 +torchvision==0.16.0 From 7b70d270325320e8107837f37739d403d4415915 Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Fri, 24 May 2024 21:36:07 +0200 Subject: [PATCH 04/10] feat: configure line ending format LF for *.sh files (#2991) --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes 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 From 04f64ab0bcddb70e6f60a3a463bbbb59bd320216 Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Fri, 24 May 2024 21:58:17 +0200 Subject: [PATCH 05/10] feat: add translation for image size describe (#2992) --- language/en.json | 14 ++++++++++++-- modules/util.py | 3 ++- webui.py | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/language/en.json b/language/en.json index 3eb5d5e2..33a70b7b 100644 --- a/language/en.json +++ b/language/en.json @@ -9,9 +9,19 @@ "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", + "Drag inpaint or outpaint image to here": "Drag inpaint or outpaint image to here", + "Outpaint Direction": "Outpaint Direction", + "Method": "Method", + "Describe": "Describe", + "Drag any image to here": "Drag any image to here", + "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:", + "Drag above image to here": "Drag above image to here", "Disabled": "Disabled", "Vary (Subtle)": "Vary (Subtle)", "Vary (Strong)": "Vary (Strong)", diff --git a/modules/util.py b/modules/util.py index 4f975bf5..8317dd50 100644 --- a/modules/util.py +++ b/modules/util.py @@ -494,7 +494,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/webui.py b/webui.py index 7606e010..25e57222 100644 --- a/webui.py +++ b/webui.py @@ -221,7 +221,7 @@ with shared.gradio_root: 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): From d850bca09fc8a1bac6635980035862ded4dd4b18 Mon Sep 17 00:00:00 2001 From: Alexdnk <83111151+Alexdnk@users.noreply.github.com> Date: Sat, 25 May 2024 03:05:28 +0700 Subject: [PATCH 06/10] feat: read value 'CFG Mimicking from TSNR' (adaptive_cfg) from presets (#2990) --- modules/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/config.py b/modules/config.py index 64b0b86f..08ed99d7 100644 --- a/modules/config.py +++ b/modules/config.py @@ -487,6 +487,7 @@ possible_preset_keys = { "default_loras": "", "default_cfg_scale": "guidance_scale", "default_sample_sharpness": "sharpness", + "default_cfg_tsnr": "adaptive_cfg", "default_sampler": "sampler", "default_scheduler": "scheduler", "default_overwrite_step": "steps", From 1d1a4a3ebd2ea06fa396be670272ee9659e5f66c Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Sun, 26 May 2024 11:40:15 +0200 Subject: [PATCH 07/10] feat: add inpaint color picker (#2997) Workaround as tool color-sketch applies changes directly to the image canvas and not the mask canvas. Color picker is not correctly implemented in Gradio 3.41.2 => does always get displayed as separate containers and not merged with other elements --- css/style.css | 6 +++++- webui.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index b5f7a448..b82cf930 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,8 @@ progress::after { text-align: center; border-radius: 5px 5px 0px 0px; display: none; /* remove this to enable tooltip in preview image */ +} + +#inpaint_brush_color input[type=color]{ + background: none; } \ No newline at end of file diff --git a/webui.py b/webui.py index 25e57222..ae0bc89f 100644 --- a/webui.py +++ b/webui.py @@ -524,13 +524,20 @@ 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] inpaint_mask_upload_checkbox.change(lambda x: gr.update(visible=x), - inputs=inpaint_mask_upload_checkbox, - outputs=inpaint_mask_image, queue=False, show_progress=False) + inputs=inpaint_mask_upload_checkbox, + outputs=inpaint_mask_image, 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) From 4e5509351f3882431e6088cdc7ec3632534df2e4 Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Sun, 26 May 2024 11:47:33 +0200 Subject: [PATCH 08/10] feat: remove labels from most of the image input fields (#2998) --- css/style.css | 4 ++++ language/en.json | 5 +---- webui.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/css/style.css b/css/style.css index b82cf930..649f77c5 100644 --- a/css/style.css +++ b/css/style.css @@ -401,6 +401,10 @@ progress::after { 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/language/en.json b/language/en.json index 33a70b7b..90eaf2ee 100644 --- a/language/en.json +++ b/language/en.json @@ -10,18 +10,15 @@ "Upscale or Variation": "Upscale or Variation", "Image Prompt": "Image Prompt", "Inpaint or Outpaint": "Inpaint or Outpaint", - "Drag inpaint or outpaint image to here": "Drag inpaint or outpaint image to here", "Outpaint Direction": "Outpaint Direction", "Method": "Method", "Describe": "Describe", - "Drag any image to here": "Drag any image to here", "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:", - "Drag above image to here": "Drag above image to here", "Disabled": "Disabled", "Vary (Subtle)": "Vary (Subtle)", "Vary (Strong)": "Vary (Strong)", @@ -394,7 +391,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/webui.py b/webui.py index ae0bc89f..b475cd90 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') @@ -201,7 +201,7 @@ with shared.gradio_root: queue=False, show_progress=False) with gr.TabItem(label='Inpaint or Outpaint') as inpaint_tab: with gr.Row(): - 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_mask_image = grh.Image(label='Mask Upload', source='upload', type='numpy', height=500, visible=False) with gr.Row(): @@ -214,7 +214,7 @@ 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', @@ -233,7 +233,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') From cc58fe52706a5a9ec75ad12f9643e19fe170e253 Mon Sep 17 00:00:00 2001 From: Manuel Schmid <9307310+mashb1t@users.noreply.github.com> Date: Sun, 26 May 2024 14:18:19 +0200 Subject: [PATCH 09/10] feat: add clip skip handling (#2999) --- language/en.json | 1 + modules/async_worker.py | 6 ++++++ modules/config.py | 6 ++++++ modules/default_pipeline.py | 11 +++++++++++ modules/meta_parser.py | 18 ++++++++++-------- webui.py | 11 +++++++---- 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/language/en.json b/language/en.json index 90eaf2ee..a4056e1e 100644 --- a/language/en.json +++ b/language/en.json @@ -320,6 +320,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.", diff --git a/modules/async_worker.py b/modules/async_worker.py index 594886d2..d7d9b9fd 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -174,6 +174,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() @@ -297,6 +298,7 @@ def worker(): adm_scaler_end = 0.0 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 = ' @@ -466,6 +468,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 = [] @@ -924,6 +928,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 08ed99d7..0aee2713 100644 --- a/modules/config.py +++ b/modules/config.py @@ -434,6 +434,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, @@ -488,6 +493,7 @@ possible_preset_keys = { "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 4ce12435..586e62da 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()) @@ -314,6 +315,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', @@ -458,7 +460,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/webui.py b/webui.py index b475cd90..d72eb2ec 100644 --- a/webui.py +++ b/webui.py @@ -412,6 +412,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, @@ -576,9 +579,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): @@ -663,7 +666,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] From 67289dd0fef248650cefd87729347032c443e0cc Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Sun, 26 May 2024 15:11:40 +0200 Subject: [PATCH 10/10] release: bump version to 2.4.0, update changelog --- fooocus_version.py | 2 +- update_log.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/fooocus_version.py b/fooocus_version.py index 41556f90..ecc15807 100644 --- a/fooocus_version.py +++ b/fooocus_version.py @@ -1 +1 @@ -version = '2.4.0-rc2' +version = '2.4.0' 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