mirror of
https://github.com/lllyasviel/Fooocus.git
synced 2026-08-16 13:13:16 +02:00
feat: add metadata to images (#1940)
* feat: add metadata logging for images inspired by https://github.com/MoonRide303/Fooocus-MRE * feat: add config and checkbox for save_metadata_to_images * feat: add argument disable_metadata * feat: add support for A1111 metadata schema https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cf2772fab0af5573da775e7437e6acdca424f26e/modules/processing.py#L672 * feat: add model hash support for a1111 * feat: use resolved prompts with included expansion and styles for a1111 metadata * fix: code cleanup and resolved prompt fixes * feat: add config metadata_created_by * fix: use stting isntead of quote wrap for A1111 created_by * fix: correctlyy hide/show metadata schema on app start * fix: do not generate hashes when arg --disable-metadata is used * refactor: rename metadata_schema to metadata_scheme * fix: use pnginfo "parameters" insteadf of "Comments" see https://github.com/RupertAvery/DiffusionToolkit/issues/202 and https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cf2772fab0af5573da775e7437e6acdca424f26e/modules/processing.py#L939 * feat: add resolved prompts to metadata * fix: use correct default value in metadata check for created_by * wip: add metadata mapping, reading and writing applying data after reading currently not functional for A1111 * feat: rename metadata tab and import button label * feat: map basic information for scheme A1111 * wip: optimize handling for metadata in Gradio calls * feat: add enums for Performance, Steps and StepsUOV also move MetadataSchema enum to prevent circular dependency * fix: correctly map resolution, use empty styles for A1111 * chore: code cleanup * feat: add A1111 prompt style detection only detects one style as Fooocus doesn't wrap {prompt} with the whole style, but has a separate prompt string for each style * wip: add prompt style extraction for A1111 scheme * feat: sort styles after metadata import * refactor: use central flag for LoRA count * refactor: use central flag for ControlNet image count * fix: use correct LoRA mapping, add fallback for backwards compatibility * feat: add created_by again * feat: add prefix "Fooocus" to version * wip: code cleanup, update todos * fix: use correct order to read LoRA in meta parser * wip: code cleanup, update todos * feat: make sha256 with length 10 default * feat: add lora handling to A1111 scheme * feat: override existing LoRA values when importing, would cause images to differ * fix: correctly extract prompt style when only prompt expansion is selected * feat: allow model / LoRA loading from subfolders * feat: code cleanup, do not queue metadata preview on image upload * refactor: add flag for refiner_swap_method * feat: add metadata handling for all non-img2img parameters * refactor: code cleanup * chore: use str as return type in calculate_sha256 * feat: add hash cache to metadata * chore: code cleanup * feat: add method get_scheme to Metadata * fix: align handling for scheme Fooocus by removing lcm lora from json parsing * refactor: add step before parsing to set data in parser - add constructor for MetadataSchema class - remove showable and copyable from log output - add functional hash cache (model hashing takes about 5 seconds, only required once per model, using hash lazy loading) * feat: sort metadata attributes before writing to image * feat: add translations and hint for image prompt parameters * chore: check and remove ToDo's * refactor: merge metadata.py into meta_parser.py * fix: add missing refiner in A1111 parse_json * wip: add TODO for ultiline prompt style resolution * fix: remove sorting for A1111, change performance key position fixes https://github.com/lllyasviel/Fooocus/pull/1940#issuecomment-1924444633 * fix: add workaround for multiline prompts * feat: add sampler mapping * feat: prevent config reset by renaming metadata_scheme to match config options * chore: remove remaining todos after analysis refiner is added when set restoring multiline prompts has been resolved by using separate parameters "raw_prompt" and "raw_negative_prompt" * chore: specify too broad exception types * feat: add mapping for _gpu samplers to cpu samplers gpu samplers are less deterministic than cpu but in general similar, see https://www.reddit.com/r/comfyui/comments/15hayzo/comment/juqcpep/ * feat: add better handling for image import with empty metadata * fix: parse adaptive_cfg as float instead of string * chore: loosen strict type for parse_json, fix indent * chore: make steps enums more strict * feat: only override steps if metadata value is not in steps enum or in steps enum and performance is not the same * fix: handle empty strings in metadata e.g. raw negative prompt when none is set
This commit is contained in:
@@ -20,6 +20,7 @@ from modules.sdxl_styles import legal_style_names
|
||||
from modules.private_logger import get_current_html_path
|
||||
from modules.ui_gradio_extensions import reload_javascript
|
||||
from modules.auth import auth_enabled, check_auth
|
||||
from modules.util import is_json
|
||||
|
||||
def get_task(*args):
|
||||
args = list(args)
|
||||
@@ -158,7 +159,7 @@ with shared.gradio_root:
|
||||
ip_weights = []
|
||||
ip_ctrls = []
|
||||
ip_ad_cols = []
|
||||
for _ in range(4):
|
||||
for _ in range(flags.controlnet_image_count):
|
||||
with gr.Column():
|
||||
ip_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False, height=300)
|
||||
ip_images.append(ip_image)
|
||||
@@ -216,6 +217,30 @@ with shared.gradio_root:
|
||||
value=flags.desc_type_photo)
|
||||
desc_btn = gr.Button(value='Describe this Image into Prompt')
|
||||
gr.HTML('<a href="https://github.com/lllyasviel/Fooocus/discussions/1363" target="_blank">\U0001F4D4 Document</a>')
|
||||
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_json = gr.JSON(label='Metadata')
|
||||
metadata_import_button = gr.Button(value='Apply Metadata')
|
||||
|
||||
def trigger_metadata_preview(filepath):
|
||||
parameters, items, metadata_scheme = modules.meta_parser.read_info_from_image(filepath)
|
||||
|
||||
results = {}
|
||||
if parameters is not None:
|
||||
results['parameters'] = parameters
|
||||
|
||||
if items:
|
||||
results['items'] = items
|
||||
|
||||
if isinstance(metadata_scheme, flags.MetadataScheme):
|
||||
results['metadata_scheme'] = metadata_scheme.value
|
||||
|
||||
return results
|
||||
|
||||
metadata_input_image.upload(trigger_metadata_preview, inputs=metadata_input_image,
|
||||
outputs=metadata_json, queue=False, show_progress=True)
|
||||
|
||||
switch_js = "(x) => {if(x){viewer_to_bottom(100);viewer_to_bottom(500);}else{viewer_to_top();} return x;}"
|
||||
down_js = "() => {viewer_to_bottom();}"
|
||||
|
||||
@@ -359,7 +384,7 @@ with shared.gradio_root:
|
||||
step=0.001, value=0.3,
|
||||
info='When to end the guidance from positive/negative ADM. ')
|
||||
|
||||
refiner_swap_method = gr.Dropdown(label='Refiner swap method', value='joint',
|
||||
refiner_swap_method = gr.Dropdown(label='Refiner swap method', value=flags.refiner_swap_method,
|
||||
choices=['joint', 'separate', 'vae'])
|
||||
|
||||
adaptive_cfg = gr.Slider(label='CFG Mimicking from TSNR', minimum=1.0, maximum=30.0, step=0.01,
|
||||
@@ -407,6 +432,16 @@ with shared.gradio_root:
|
||||
info='Disable automatic seed increment when image number is > 1.',
|
||||
value=False)
|
||||
|
||||
if not args_manager.args.disable_metadata:
|
||||
save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images,
|
||||
info='Adds parameters to generated images allowing manual regeneration.')
|
||||
metadata_scheme = gr.Radio(label='Metadata Scheme', choices=flags.metadata_scheme, value=modules.config.default_metadata_scheme,
|
||||
info='Image Prompt parameters are not included. Use a1111 for compatibility with Civitai.',
|
||||
visible=modules.config.default_save_metadata_to_images)
|
||||
|
||||
save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_scheme],
|
||||
queue=False, show_progress=False)
|
||||
|
||||
with gr.Tab(label='Control'):
|
||||
debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False,
|
||||
info='See the results from preprocessors.')
|
||||
@@ -484,7 +519,6 @@ with shared.gradio_root:
|
||||
results += [gr.update(choices=['None'] + modules.config.model_filenames)]
|
||||
for i in range(modules.config.default_max_lora_number):
|
||||
results += [gr.update(interactive=True), gr.update(choices=['None'] + modules.config.lora_filenames), gr.update()]
|
||||
return results
|
||||
|
||||
model_refresh.click(model_refresh_clicked, [], [base_model, refiner_model] + lora_ctrls,
|
||||
queue=False, show_progress=False)
|
||||
@@ -555,20 +589,18 @@ with shared.gradio_root:
|
||||
ctrls += [refiner_swap_method, controlnet_softness]
|
||||
ctrls += freeu_ctrls
|
||||
ctrls += inpaint_ctrls
|
||||
|
||||
if not args_manager.args.disable_metadata:
|
||||
ctrls += [save_metadata_to_images, metadata_scheme]
|
||||
|
||||
ctrls += ip_ctrls
|
||||
|
||||
state_is_generating = gr.State(False)
|
||||
|
||||
def parse_meta(raw_prompt_txt, is_generating):
|
||||
loaded_json = None
|
||||
try:
|
||||
if '{' in raw_prompt_txt:
|
||||
if '}' in raw_prompt_txt:
|
||||
if ':' in raw_prompt_txt:
|
||||
loaded_json = json.loads(raw_prompt_txt)
|
||||
assert isinstance(loaded_json, dict)
|
||||
except:
|
||||
loaded_json = None
|
||||
if is_json(raw_prompt_txt):
|
||||
loaded_json = json.loads(raw_prompt_txt)
|
||||
|
||||
if loaded_json is None:
|
||||
if is_generating:
|
||||
@@ -580,31 +612,29 @@ with shared.gradio_root:
|
||||
|
||||
prompt.input(parse_meta, inputs=[prompt, state_is_generating], outputs=[prompt, generate_button, load_parameter_button], queue=False, show_progress=False)
|
||||
|
||||
load_parameter_button.click(modules.meta_parser.load_parameter_button_click, inputs=[prompt, state_is_generating], outputs=[
|
||||
advanced_checkbox,
|
||||
image_number,
|
||||
prompt,
|
||||
negative_prompt,
|
||||
style_selections,
|
||||
performance_selection,
|
||||
aspect_ratios_selection,
|
||||
overwrite_width,
|
||||
overwrite_height,
|
||||
sharpness,
|
||||
guidance_scale,
|
||||
adm_scaler_positive,
|
||||
adm_scaler_negative,
|
||||
adm_scaler_end,
|
||||
base_model,
|
||||
refiner_model,
|
||||
refiner_switch,
|
||||
sampler_name,
|
||||
scheduler_name,
|
||||
seed_random,
|
||||
image_seed,
|
||||
generate_button,
|
||||
load_parameter_button
|
||||
] + lora_ctrls, queue=False, show_progress=False)
|
||||
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, seed_random, image_seed,
|
||||
generate_button, load_parameter_button] + freeu_ctrls + lora_ctrls
|
||||
|
||||
load_parameter_button.click(modules.meta_parser.load_parameter_button_click, inputs=[prompt, state_is_generating], outputs=load_data_outputs, queue=False, show_progress=False)
|
||||
|
||||
def trigger_metadata_import(filepath, state_is_generating):
|
||||
parameters, items, metadata_scheme = modules.meta_parser.read_info_from_image(filepath)
|
||||
if parameters is None:
|
||||
print('Could not find metadata in the image!')
|
||||
parsed_parameters = {}
|
||||
else:
|
||||
metadata_parser = modules.meta_parser.get_metadata_parser(metadata_scheme)
|
||||
parsed_parameters = metadata_parser.parse_json(parameters)
|
||||
|
||||
return modules.meta_parser.load_parameter_button_click(parsed_parameters, state_is_generating)
|
||||
|
||||
|
||||
metadata_import_button.click(trigger_metadata_import, inputs=[metadata_input_image, state_is_generating], outputs=load_data_outputs, queue=False, show_progress=True) \
|
||||
.then(style_sorter.sort_styles, inputs=style_selections, outputs=style_selections, queue=False, show_progress=False)
|
||||
|
||||
generate_button.click(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), [], True),
|
||||
outputs=[stop_button, skip_button, generate_button, gallery, state_is_generating]) \
|
||||
|
||||
Reference in New Issue
Block a user