from typing import List
from modules import shared, script_callbacks
from modules.shared import opts, cmd_opts
import gradio as gr
from scripts.dataset_tag_editor.dataset_tag_editor import DatasetTagEditor, interrogate_image_clip, interrogate_image_booru, InterrogateMethod
dataset_tag_editor = DatasetTagEditor()
total_image_num = 0
displayed_image_num = 0
current_tag_filter = ''
current_selection = 0
tmp_selection_img_path_set = set()
selected_image_path = ''
selection_selected_image_path = ''
# ================================================================
# Callbacks for "Filter and Edit Tags" tab
# ================================================================
def arrange_tag_order(tags: List[str], sort_by: str, sort_order: str) -> List[str]:
tags = dataset_tag_editor.sort_tags(tags=tags, sort_by=sort_by, sort_order=sort_order)
return tags
def get_current_txt_filter():
global total_image_num, displayed_image_num, current_tag_filter, current_selection, selected_image_path
return f"""
Displayed Images : {displayed_image_num} / {total_image_num} total
Current Tag Filter : {current_tag_filter}
Current Selection Filter : {current_selection} images
Selected Image : {selected_image_path}
"""
def get_current_txt_selection():
global selection_selected_image_path
return f"""Selected Image : {selection_selected_image_path}"""
def load_files_from_dir(dir: str, sort_by: str, sort_order: str, recursive: bool, load_caption_from_filename: bool, use_interrogator: str, use_clip: bool, use_booru: bool):
global total_image_num, displayed_image_num, current_tag_filter, current_selection, tmp_selection_img_path_set, selected_image_path, selection_selected_image_path
interrogate_method = InterrogateMethod.NONE
if use_interrogator == 'If Empty':
interrogate_method = InterrogateMethod.PREFILL
elif use_interrogator == 'Overwrite':
interrogate_method = InterrogateMethod.OVERWRITE
elif use_interrogator == 'Prepend':
interrogate_method = InterrogateMethod.PREPEND
elif use_interrogator == 'Append':
interrogate_method = InterrogateMethod.APPEND
dataset_tag_editor.load_dataset(img_dir=dir, recursive=recursive, load_caption_from_filename=load_caption_from_filename, interrogate_method=interrogate_method, use_clip=use_clip, use_booru=use_booru)
img_paths, tags = dataset_tag_editor.get_filtered_imgpath_and_tags()
tags = arrange_tag_order(tags=tags, sort_by=sort_by, sort_order=sort_order)
total_image_num = displayed_image_num = len(dataset_tag_editor.get_img_path_set())
current_tag_filter = ''
tmp_selection_img_path_set = set()
current_selection = 0
selected_image_path = ''
selection_selected_image_path = ''
return [
img_paths,
[],
gr.CheckboxGroup.update(value=None, choices=dataset_tag_editor.write_tags(tags)),
'',
get_current_txt_filter(),
get_current_txt_selection()
]
def search_tags(filter_tags: List[str], filter_word: str, sort_by: str, sort_order: str):
filter_tags = dataset_tag_editor.read_tags(filter_tags)
_, tags = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags, filter_word=filter_word)
tags = arrange_tag_order(tags, sort_by=sort_by, sort_order=sort_order)
return gr.CheckboxGroup.update(choices=dataset_tag_editor.write_tags(tags))
def clear_tag_filters(sort_by, sort_order):
return filter_gallery(filter_tags=[], filter_word='', sort_by=sort_by, sort_order=sort_order) + ['']
def rearrange_tag_order(filter_tags: List[str], filter_word: str, sort_by: str, sort_order: str):
filter_tags = dataset_tag_editor.read_tags(filter_tags)
_, tags = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags, filter_word=filter_word)
tags = arrange_tag_order(tags=tags, sort_by=sort_by, sort_order=sort_order)
return gr.CheckboxGroup.update(choices=dataset_tag_editor.write_tags(tags))
def filter_gallery_by_checkbox(filter_tags: List[str], filter_word: str, sort_by: str, sort_order: str):
filter_tags = dataset_tag_editor.read_tags(filter_tags)
return filter_gallery(filter_tags=filter_tags, filter_word=filter_word, sort_by=sort_by, sort_order=sort_order)
def filter_gallery(filter_tags: List[str], filter_word: str, sort_by: str, sort_order: str):
global displayed_image_num, total_image_num, current_tag_filter, current_selection, selected_image_path
img_paths, tags = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags, filter_word=filter_word)
current_tag_filter = ', '.join(filter_tags) if filter_tags else ''
displayed_image_num = len(img_paths)
tags = arrange_tag_order(tags=tags, sort_by=sort_by, sort_order=sort_order)
filter_tags = dataset_tag_editor.write_tags(filter_tags)
tags = dataset_tag_editor.write_tags(tags)
current_selection = len(tmp_selection_img_path_set)
if filter_tags and len(filter_tags) == 0:
filter_tags = None
return [
img_paths,
gr.CheckboxGroup.update(value=filter_tags, choices=tags),
current_tag_filter,
current_tag_filter,
-1,
get_current_txt_filter()
]
def apply_edit_tags(edit_tags: str, filter_tags: List[str], prepend: bool, filter_word: str, sort_by: str, sort_order: str):
replace_tags = [t.strip() for t in edit_tags.split(',')]
filter_tags = dataset_tag_editor.read_tags(filter_tags)
dataset_tag_editor.replace_tags(search_tags = filter_tags, replace_tags = replace_tags, filter_tags = filter_tags, prepend = prepend)
replace_tags = [t for t in replace_tags if t]
return filter_gallery(filter_tags = replace_tags, filter_word = filter_word, sort_by=sort_by, sort_order=sort_order)
def save_all_changes(backup: bool) -> str:
saved, total, dir = dataset_tag_editor.save_dataset(backup=backup)
return f'Saved text files : {saved}/{total} under {dir}' if total > 0 else ''
# ================================================================
# Callbacks for "Filter by Selection" tab
# ================================================================
def arrange_selection_order(paths: List[str]) -> List[str]:
return sorted(paths)
def selection_index_changed(idx: int):
global tmp_selection_img_path_set, selection_selected_image_path
idx = int(idx)
img_paths = arrange_selection_order(tmp_selection_img_path_set)
if idx < 0 or len(img_paths) <= idx:
selection_selected_image_path = ''
idx = -1
else:
selection_selected_image_path = img_paths[idx]
return [get_current_txt_selection(), idx]
def add_image_selection(filter_tags: List[str], idx: int):
global tmp_selection_img_path_set
idx = int(idx)
filter_tags = dataset_tag_editor.read_tags(filter_tags)
img_paths, _ = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags)
if idx < 0 or len(img_paths) <= idx:
idx = -1
else:
tmp_selection_img_path_set.add(img_paths[idx])
return [arrange_selection_order(tmp_selection_img_path_set), idx]
def invert_image_selection():
global tmp_selection_img_path_set
img_paths = dataset_tag_editor.get_img_path_set()
tmp_selection_img_path_set = img_paths - tmp_selection_img_path_set
return arrange_selection_order(tmp_selection_img_path_set)
def remove_image_selection(idx: int):
global tmp_selection_img_path_set, selection_selected_image_path
idx = int(idx)
img_paths = arrange_selection_order(tmp_selection_img_path_set)
if idx < 0 or len(img_paths) <= idx:
idx = -1
else:
tmp_selection_img_path_set.remove(img_paths[idx])
selection_selected_image_path = ''
return [
arrange_selection_order(tmp_selection_img_path_set),
get_current_txt_selection(),
idx
]
def clear_image_selection():
global tmp_selection_img_path_set, selection_selected_image_path
tmp_selection_img_path_set.clear()
selection_selected_image_path = ''
dataset_tag_editor.set_img_filter_img_path()
return[
[],
get_current_txt_selection(),
-1
]
def apply_image_selection_filter(filter_tags: List[str], filter_word: str, sort_by: str, sort_order: str):
global tmp_selection_img_path_set
filter_tags = dataset_tag_editor.read_tags(filter_tags)
dataset_tag_editor.set_img_filter_img_path(tmp_selection_img_path_set)
return filter_gallery(filter_tags=filter_tags, filter_word=filter_word, sort_by=sort_by, sort_order=sort_order)
# ================================================================
# Callbacks for "Edit Caption of Selected Image" tab
# ================================================================
def gallery_index_changed(filter_tags: List[str], idx: int):
global displayed_image_num, total_image_num, current_tag_filter, current_selection, selected_image_path
idx = int(idx)
filter_tags = dataset_tag_editor.read_tags(filter_tags)
img_paths, _ = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags)
tags_txt = ''
if 0 <= idx and idx < len(img_paths):
selected_image_path = img_paths[idx]
tags_txt = ', '.join(dataset_tag_editor.get_tags_by_image_path(selected_image_path))
else:
selected_image_path = ''
idx = -1
return [
tags_txt,
get_current_txt_filter(),
idx
]
def change_tags_selected_image(tags_text: str, filter_tags: List[str], sort_by: str, sort_order: str, idx: int):
idx = int(idx)
filter_tags = dataset_tag_editor.read_tags(filter_tags)
img_paths, _ = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags)
edited_tags = [t.strip() for t in tags_text.split(',')]
if idx < 0 or len(img_paths) <= idx:
return [gr.CheckboxGroup.update(), -1]
else:
dataset_tag_editor.set_tags_by_image_path(imgpath=img_paths[idx], tags=edited_tags)
_, tags = dataset_tag_editor.get_filtered_imgpath_and_tags(filter_tags=filter_tags)
tags = arrange_tag_order(tags=tags, sort_by=sort_by, sort_order=sort_order)
return [gr.CheckboxGroup.update(value=dataset_tag_editor.write_tags(filter_tags), choices=dataset_tag_editor.write_tags(tags)), idx]
def interrogate_selected_image_clip():
global selected_image_path
return interrogate_image_clip(selected_image_path)
def interrogate_selected_image_booru():
global selected_image_path
return interrogate_image_booru(selected_image_path)
# ================================================================
# Script Callbacks
# ================================================================
def on_ui_tabs():
global displayed_image_num, total_image_num, current_tag_filter, current_selection, selected_image_path, selection_selected_image_path
with gr.Blocks(analytics_enabled=False) as dataset_tag_editor_interface:
with gr.Row(visible=False):
btn_hidden_set_index = gr.Button(elem_id="dataset_tag_editor_btn_hidden_set_index")
lbl_hidden_image_index = gr.Label(value=-1)
gr.HTML(value="""
This extension works well with text captions in comma-separated style (such as the tags generated by DeepBooru interrogator).
""")
with gr.Column(variant='panel'):
with gr.Row():
with gr.Column(scale=1):
btn_save_all_changes = gr.Button(value='Save all changes', variant='primary')
with gr.Column(scale=2):
cb_backup = gr.Checkbox(value=True, label='Backup original text file (original file will be renamed like filename.000, .001, .002, ...)', interactive=True)
gr.HTML(value='Note: New text file will be created if you are using filename as captions.')
txt_result = gr.Textbox(label='Results', interactive=False)
with gr.Row().style(equal_height=False):
with gr.Column():
with gr.Column(variant='panel'):
with gr.Row():
with gr.Column(scale=3):
tb_img_directory = gr.Textbox(label='Dataset directory', placeholder='C:\\directory\\of\\datasets')
with gr.Column(scale=1, min_width=80):
btn_load_datasets = gr.Button(value='Load')
with gr.Row():
with gr.Column():
cb_load_recursive = gr.Checkbox(value=False, label='Load from subdirectories')
cb_load_caption_from_filename = gr.Checkbox(value=True, label='Load caption from filename if no text file exists')
with gr.Column():
rb_use_interrogator = gr.Radio(choices=['No', 'If Empty', 'Overwrite', 'Prepend', 'Append'], value='No', label='Use Interrogator Caption')
with gr.Row():
cb_use_clip_to_prefill = gr.Checkbox(value=False, label='Use BLIP')
cb_use_booru_to_prefill = gr.Checkbox(value=False, label='Use DeepDanbooru')
gl_dataset_images = gr.Gallery(label='Dataset Images', elem_id="dataset_tag_editor_dataset_gallery").style(grid=opts.dataset_editor_image_columns)
txt_filter = gr.HTML(value=get_current_txt_filter())
with gr.Tab(label='Filter and Edit Tags'):
with gr.Column():
with gr.Row():
btn_clear_tag_filters = gr.Button(value='Clear tag filters')
btn_clear_all_filters = gr.Button(value='Clear ALL filters')
with gr.Column(variant='panel'):
gr.HTML(value='Edit tags in filtered images (=displayed images)')
tb_selected_tags = gr.Textbox(label='Selected Tags', interactive=False)
tb_edit_tags = gr.Textbox(label='Edit Tags', interactive=True)
cb_prepend_tags = gr.Checkbox(value=False, label='Prepend additional tags')
btn_apply_edit_tags = gr.Button(value='Apply changes to filtered images', variant='primary')
with gr.Accordion(label='Show description of how to edit tags', open=False):
gr.HTML(value="""
1. The selected tags are displayed in comma separated style.
2. When changes are applied, all tags in each displayed images are replaced.
3. If you change some tags into blank, they will be erased.
4. If you add some tags to the end, they will be added to the end/beginning of the text file.
5. Changes are not applied to the text files until the "Save all changes" button is pressed.
ex A.
Original Text = "A, A, B, C" Selected Tags = "B, A" Edit Tags = "X, Y"
Result = "Y, Y, X, C" (B->X, A->Y)
ex B.
Original Text = "A, B, C" Selected Tags = "(nothing)" Edit Tags = "X, Y"
Result = "A, B, C, X, Y" (add X and Y to the end (default))
Result = "X, Y, A, B, C" (add X and Y to the beginning ("Prepend additional tags" checked))
ex C.
Original Text = "A, B, C, D, E" Selected Tags = "A, B, D" Edit Tags = ", X, "
Result = "X, C, E" (A->"", B->X, D->"")
""")
with gr.Column(variant='panel'):
gr.HTML(value='Search tags / Filter images by tags')
tb_search_tags = gr.Textbox(label='Search Tags', interactive=True)
with gr.Row():
rd_sort_by = gr.Radio(choices=['Alphabetical Order', 'Frequency'], value='Alphabetical Order', interactive=True, label='Sort by')
rd_sort_order = gr.Radio(choices=['Ascending', 'Descending'], value='Ascending', interactive=True, label='Sort Order')
cbg_tags = gr.CheckboxGroup(label='Filter Images by Tags', interactive=True)
with gr.Tab(label='Filter by Selection'):
with gr.Row(visible=False):
btn_hidden_set_selection_index = gr.Button(elem_id="dataset_tag_editor_btn_hidden_set_selection_index")
lbl_hidden_selection_image_index = gr.Label(value=-1)
gr.HTML("""Select images from the left gallery.""")
with gr.Column(variant='panel'):
with gr.Row():
btn_add_image_selection = gr.Button(value='Add selection [Enter]', elem_id='dataset_tag_editor_btn_add_image_selection')
gl_selected_images = gr.Gallery(label='Filter Images', elem_id="dataset_tag_editor_selection_gallery").style(grid=opts.dataset_editor_image_columns)
txt_selection = gr.HTML(value=get_current_txt_selection())
with gr.Row():
btn_remove_image_selection = gr.Button(value='Remove selection [Delete]', elem_id='dataset_tag_editor_btn_remove_image_selection')
btn_invert_image_selection = gr.Button(value='Invert selection')
btn_clear_image_selection = gr.Button(value='Clear selection')
btn_apply_image_selection_filter = gr.Button(value='Apply selection filter', variant='primary')
with gr.Tab(label='Edit Caption of Selected Image'):
with gr.Tab(label='Read Caption from Selected Image'):
tb_caption_selected_image = gr.Textbox(label='Caption of Selected Image', interactive=True, lines=6)
with gr.Row():
btn_copy_caption = gr.Button(value='Copy and Overwrite')
btn_prepend_caption = gr.Button(value='Prepend')
btn_append_caption = gr.Button(value='Append')
with gr.Tab(label='Interrogate Selected Image'):
with gr.Row():
btn_interrogate_clip = gr.Button(value='Interrogate with BLIP')
btn_interrogate_booru = gr.Button(value='Interrogate with DeepDanbooru')
tb_interrogate_selected_image = gr.Textbox(label='Interrogate Result', interactive=True, lines=6)
with gr.Row():
btn_copy_interrogate = gr.Button(value='Copy and Overwrite')
btn_prepend_interrogate = gr.Button(value='Prepend')
btn_append_interrogate = gr.Button(value='Append')
tb_edit_caption_selected_image = gr.Textbox(label='Edit Caption', interactive=True, lines=6)
btn_apply_changes_selected_image = gr.Button(value='Apply changes to selected image', variant='primary')
gr.HTML("""Changes are not applied to the text files until the "Save all changes" button is pressed.""")
#----------------------------------------------------------------
# Filter and Edit Tags tab
btn_save_all_changes.click(
fn=save_all_changes,
inputs=[cb_backup],
outputs=[txt_result]
)
btn_load_datasets.click(
fn=load_files_from_dir,
inputs=[tb_img_directory, rd_sort_by, rd_sort_order, cb_load_recursive, cb_load_caption_from_filename, rb_use_interrogator, cb_use_clip_to_prefill, cb_use_booru_to_prefill],
outputs=[gl_dataset_images, gl_selected_images, cbg_tags, tb_search_tags, txt_filter, txt_selection]
)
btn_load_datasets.click(
fn=lambda:['', '', '', -1],
inputs=[],
outputs=[tb_selected_tags, tb_edit_tags, tb_caption_selected_image, lbl_hidden_image_index]
)
cbg_tags.change(
fn=filter_gallery_by_checkbox,
inputs=[cbg_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=[gl_dataset_images, cbg_tags, tb_selected_tags, tb_edit_tags, lbl_hidden_image_index, txt_filter]
)
rd_sort_by.change(
fn=rearrange_tag_order,
inputs=[cbg_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=cbg_tags
)
rd_sort_order.change(
fn=rearrange_tag_order,
inputs=[cbg_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=cbg_tags
)
tb_search_tags.change(
fn=search_tags,
inputs=[cbg_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=cbg_tags
)
btn_apply_edit_tags.click(
fn=apply_edit_tags,
inputs=[tb_edit_tags, cbg_tags, cb_prepend_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=[gl_dataset_images, cbg_tags, tb_selected_tags, tb_edit_tags, lbl_hidden_image_index, txt_filter]
)
btn_clear_tag_filters.click(
fn=clear_tag_filters,
inputs=[rd_sort_by, rd_sort_order],
outputs=[gl_dataset_images, cbg_tags, tb_selected_tags, tb_edit_tags, lbl_hidden_image_index, txt_filter, tb_search_tags]
)
btn_clear_all_filters.click(
fn=clear_image_selection,
inputs=None,
outputs=[gl_selected_images, txt_selection, lbl_hidden_selection_image_index]
)
btn_clear_all_filters.click(
fn=clear_tag_filters,
inputs=[rd_sort_by, rd_sort_order],
outputs=[gl_dataset_images, cbg_tags, tb_selected_tags, tb_edit_tags, lbl_hidden_image_index, txt_filter, tb_search_tags]
)
#----------------------------------------------------------------
# Filter by Selection tab
btn_hidden_set_selection_index.click(
fn=selection_index_changed,
_js="(x) => [dataset_tag_editor_gl_selected_images_selected_index()]",
inputs=[lbl_hidden_selection_image_index],
outputs=[txt_selection, lbl_hidden_selection_image_index]
)
btn_add_image_selection.click(
fn=add_image_selection,
_js="(x, y) => [x, dataset_tag_editor_gl_dataset_images_selected_index()]",
inputs=[cbg_tags, lbl_hidden_image_index],
outputs=[gl_selected_images, lbl_hidden_image_index]
)
btn_invert_image_selection.click(
fn=invert_image_selection,
inputs=None,
outputs=gl_selected_images
)
btn_remove_image_selection.click(
fn=remove_image_selection,
_js="(x) => [dataset_tag_editor_gl_selected_images_selected_index()]",
inputs=[lbl_hidden_selection_image_index],
outputs=[gl_selected_images,txt_selection,lbl_hidden_selection_image_index]
)
btn_clear_image_selection.click(
fn=clear_image_selection,
inputs=None,
outputs=[gl_selected_images,txt_selection,lbl_hidden_selection_image_index]
)
btn_apply_image_selection_filter.click(
fn=apply_image_selection_filter,
inputs=[cbg_tags, tb_search_tags, rd_sort_by, rd_sort_order],
outputs=[gl_dataset_images, cbg_tags, tb_selected_tags, tb_edit_tags, lbl_hidden_image_index, txt_filter]
)
#----------------------------------------------------------------
# Edit Caption of Selected Image tab
btn_hidden_set_index.click(
fn=gallery_index_changed,
_js="(x, y) => [x, dataset_tag_editor_gl_dataset_images_selected_index()]",
inputs=[cbg_tags, lbl_hidden_image_index],
outputs=[tb_caption_selected_image, txt_filter, lbl_hidden_image_index]
)
btn_copy_caption.click(
fn=lambda a:a,
inputs=[tb_caption_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_append_caption.click(
fn=lambda a, b : b + (', ' if a and b else '') + a,
inputs=[tb_caption_selected_image, tb_edit_caption_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_prepend_caption.click(
fn=lambda a, b : a + (', ' if a and b else '') + b,
inputs=[tb_caption_selected_image, tb_edit_caption_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_interrogate_clip.click(
fn=interrogate_selected_image_clip,
inputs=None,
outputs=[tb_interrogate_selected_image]
)
btn_interrogate_booru.click(
fn=interrogate_selected_image_booru,
inputs=None,
outputs=[tb_interrogate_selected_image]
)
btn_copy_interrogate.click(
fn=lambda a:a,
inputs=[tb_interrogate_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_append_interrogate.click(
fn=lambda a, b : b + (', ' if a and b else '') + a,
inputs=[tb_interrogate_selected_image, tb_edit_caption_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_prepend_interrogate.click(
fn=lambda a, b : a + (', ' if a and b else '') + b,
inputs=[tb_interrogate_selected_image, tb_edit_caption_selected_image],
outputs=[tb_edit_caption_selected_image]
)
btn_apply_changes_selected_image.click(
fn=change_tags_selected_image,
_js="(a, b, c, d, e) => [a, b, c, d, dataset_tag_editor_gl_dataset_images_selected_index()]",
inputs=[tb_edit_caption_selected_image, cbg_tags, rd_sort_by, rd_sort_order, lbl_hidden_image_index],
outputs=[cbg_tags, lbl_hidden_image_index]
)
btn_apply_changes_selected_image.click(
fn=lambda a:a,
inputs=[tb_edit_caption_selected_image],
outputs=[tb_caption_selected_image]
)
return [(dataset_tag_editor_interface, "Dataset Tag Editor", "dataset_tag_editor_interface")]
def on_ui_settings():
section = ('dataset-tag-editor', "Dataset Tag Editor")
shared.opts.add_option("dataset_editor_image_columns", shared.OptionInfo(6, "Number of columns on image gallery", section=section))
script_callbacks.on_ui_settings(on_ui_settings)
script_callbacks.on_ui_tabs(on_ui_tabs)