|
import gradio as gr
|
|
from easygui import msgbox, boolbox
|
|
from .common_gui import get_folder_path, scriptdir, list_dirs
|
|
from math import ceil
|
|
import os
|
|
import re
|
|
|
|
from .custom_logging import setup_logging
|
|
|
|
|
|
log = setup_logging()
|
|
|
|
IMAGES_TO_SHOW = 5
|
|
IMAGE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
|
|
auto_save = True
|
|
|
|
|
|
def _get_caption_path(image_file, images_dir, caption_ext):
|
|
"""
|
|
Returns the expected path of a caption file for a given image path
|
|
"""
|
|
caption_file_name = os.path.splitext(image_file)[0] + caption_ext
|
|
caption_file_path = os.path.join(images_dir, caption_file_name)
|
|
return caption_file_path
|
|
|
|
|
|
def _get_quick_tags(quick_tags_text):
|
|
"""
|
|
Gets a list of tags from the quick tags text box
|
|
"""
|
|
quick_tags = [t.strip() for t in quick_tags_text.split(",") if t.strip()]
|
|
quick_tags_set = set(quick_tags)
|
|
return quick_tags, quick_tags_set
|
|
|
|
|
|
def _get_tag_checkbox_updates(caption, quick_tags, quick_tags_set):
|
|
"""
|
|
Updates a list of caption checkboxes to show possible tags and tags
|
|
already included in the caption
|
|
"""
|
|
caption_tags_have = [c.strip() for c in caption.split(",") if c.strip()]
|
|
caption_tags_unique = [t for t in caption_tags_have if t not in quick_tags_set]
|
|
caption_tags_all = quick_tags + caption_tags_unique
|
|
return gr.CheckboxGroup(choices=caption_tags_all, value=caption_tags_have)
|
|
|
|
|
|
def paginate_go(page, max_page):
|
|
try:
|
|
page = float(page)
|
|
except:
|
|
msgbox(f"Invalid page num: {page}")
|
|
return
|
|
return paginate(page, max_page, 0)
|
|
|
|
|
|
def paginate(page, max_page, page_change):
|
|
return int(max(min(page + page_change, max_page), 1))
|
|
|
|
|
|
def save_caption(caption, caption_ext, image_file, images_dir):
|
|
caption_path = _get_caption_path(image_file, images_dir, caption_ext)
|
|
with open(caption_path, "w+", encoding="utf-8") as f:
|
|
f.write(caption)
|
|
|
|
log.info(f"Wrote captions to {caption_path}")
|
|
|
|
|
|
def update_quick_tags(quick_tags_text, *image_caption_texts):
|
|
quick_tags, quick_tags_set = _get_quick_tags(quick_tags_text)
|
|
return [
|
|
_get_tag_checkbox_updates(caption, quick_tags, quick_tags_set)
|
|
for caption in image_caption_texts
|
|
]
|
|
|
|
|
|
def update_image_caption(
|
|
quick_tags_text, caption, image_file, images_dir, caption_ext, auto_save
|
|
):
|
|
if auto_save:
|
|
save_caption(caption, caption_ext, image_file, images_dir)
|
|
|
|
quick_tags, quick_tags_set = _get_quick_tags(quick_tags_text)
|
|
return _get_tag_checkbox_updates(caption, quick_tags, quick_tags_set)
|
|
|
|
|
|
def update_image_tags(
|
|
quick_tags_text,
|
|
selected_tags,
|
|
image_file,
|
|
images_dir,
|
|
caption_ext,
|
|
auto_save,
|
|
):
|
|
|
|
quick_tags, quick_tags_set = _get_quick_tags(quick_tags_text)
|
|
selected_tags_set = set(selected_tags)
|
|
|
|
output_tags = [t for t in quick_tags if t in selected_tags_set] + [
|
|
t for t in selected_tags if t not in quick_tags_set
|
|
]
|
|
caption = ", ".join(output_tags)
|
|
|
|
if auto_save:
|
|
save_caption(caption, caption_ext, image_file, images_dir)
|
|
|
|
return caption
|
|
|
|
|
|
def import_tags_from_captions(
|
|
images_dir, caption_ext, quick_tags_text, ignore_load_tags_word_count
|
|
):
|
|
"""
|
|
Scans images directory for all available captions and loads all tags
|
|
under a specified word count into the quick tags box
|
|
"""
|
|
|
|
def empty_return():
|
|
return gr.Text()
|
|
|
|
|
|
if not images_dir:
|
|
msgbox("Image folder is missing...")
|
|
return empty_return()
|
|
|
|
if not os.path.exists(images_dir):
|
|
msgbox("Image folder does not exist...")
|
|
return empty_return()
|
|
|
|
if not caption_ext:
|
|
msgbox("Please provide an extension for the caption files.")
|
|
return empty_return()
|
|
|
|
if quick_tags_text:
|
|
if not boolbox(
|
|
f"Are you sure you wish to overwrite the current quick tags?",
|
|
choices=("Yes", "No"),
|
|
):
|
|
return empty_return()
|
|
|
|
images_list = os.listdir(images_dir)
|
|
image_files = [f for f in images_list if f.lower().endswith(IMAGE_EXTENSIONS)]
|
|
|
|
|
|
tags = []
|
|
tags_set = set()
|
|
for image_file in image_files:
|
|
caption_file_path = _get_caption_path(image_file, images_dir, caption_ext)
|
|
if os.path.exists(caption_file_path):
|
|
with open(caption_file_path, "r", encoding="utf-8") as f:
|
|
caption = f.read()
|
|
for tag in caption.split(","):
|
|
tag = tag.strip()
|
|
tag_key = tag.lower()
|
|
if not tag_key in tags_set:
|
|
|
|
total_words = len(re.findall(r"\s+", tag)) + 1
|
|
if total_words <= ignore_load_tags_word_count:
|
|
tags.append(tag)
|
|
tags_set.add(tag_key)
|
|
|
|
return ", ".join(tags)
|
|
|
|
|
|
def load_images(images_dir, caption_ext, loaded_images_dir, page, max_page):
|
|
"""
|
|
Triggered to load a new set of images from the folder to caption
|
|
This loads in the total expected image counts to be used by pagination
|
|
before running update_images
|
|
"""
|
|
|
|
def empty_return():
|
|
return [loaded_images_dir, page, max_page]
|
|
|
|
|
|
if not images_dir:
|
|
msgbox("Image folder is missing...")
|
|
return empty_return()
|
|
|
|
if not os.path.exists(images_dir):
|
|
msgbox("Image folder does not exist...")
|
|
return empty_return()
|
|
|
|
if not caption_ext:
|
|
msgbox("Please provide an extension for the caption files.")
|
|
return empty_return()
|
|
|
|
|
|
images_list = os.listdir(images_dir)
|
|
total_images = len(
|
|
[True for f in images_list if f.lower().endswith(IMAGE_EXTENSIONS)]
|
|
)
|
|
return [images_dir, 1, ceil(total_images / IMAGES_TO_SHOW)]
|
|
|
|
|
|
def update_images(
|
|
images_dir,
|
|
caption_ext,
|
|
quick_tags_text,
|
|
page,
|
|
):
|
|
"""
|
|
Updates the displayed images and captions from the current page and
|
|
image directory
|
|
"""
|
|
|
|
|
|
images_list = os.listdir(images_dir)
|
|
image_files = [f for f in images_list if f.lower().endswith(IMAGE_EXTENSIONS)]
|
|
|
|
|
|
quick_tags, quick_tags_set = _get_quick_tags(quick_tags_text or "")
|
|
|
|
|
|
rows = []
|
|
image_paths = []
|
|
captions = []
|
|
tag_checkbox_groups = []
|
|
|
|
start_index = (int(page) - 1) * IMAGES_TO_SHOW
|
|
for i in range(IMAGES_TO_SHOW):
|
|
image_index = start_index + i
|
|
show_row = image_index < len(image_files)
|
|
|
|
image_path = None
|
|
caption = ""
|
|
tag_checkboxes = None
|
|
if show_row:
|
|
image_file = image_files[image_index]
|
|
image_path = os.path.join(images_dir, image_file)
|
|
|
|
caption_file_path = _get_caption_path(image_file, images_dir, caption_ext)
|
|
if os.path.exists(caption_file_path):
|
|
with open(caption_file_path, "r", encoding="utf-8") as f:
|
|
caption = f.read()
|
|
|
|
tag_checkboxes = _get_tag_checkbox_updates(caption, quick_tags, quick_tags_set)
|
|
rows.append(gr.Row(visible=show_row))
|
|
image_paths.append(image_path)
|
|
captions.append(caption)
|
|
tag_checkbox_groups.append(tag_checkboxes)
|
|
|
|
return (
|
|
rows
|
|
+ image_paths
|
|
+ image_paths
|
|
+ captions
|
|
+ tag_checkbox_groups
|
|
+ [gr.Row(visible=True), gr.Row(visible=True)]
|
|
)
|
|
|
|
|
|
|
|
def gradio_manual_caption_gui_tab(headless=False, default_images_dir=None):
|
|
from .common_gui import create_refresh_button
|
|
|
|
default_images_dir = (
|
|
default_images_dir
|
|
if default_images_dir is not None
|
|
else os.path.join(scriptdir, "data")
|
|
)
|
|
current_images_dir = default_images_dir
|
|
|
|
|
|
def list_images_dirs(path):
|
|
|
|
nonlocal current_images_dir
|
|
current_images_dir = path
|
|
return list(list_dirs(path))
|
|
|
|
with gr.Tab("Manual Captioning"):
|
|
gr.Markdown("This utility allows quick captioning and tagging of images.")
|
|
page = gr.Number(value=-1, visible=False)
|
|
max_page = gr.Number(value=1, visible=False)
|
|
loaded_images_dir = gr.Text(visible=False)
|
|
with gr.Group(), gr.Row():
|
|
images_dir = gr.Dropdown(
|
|
label="Image folder to caption (containing the images to caption)",
|
|
choices=[""] + list_images_dirs(default_images_dir),
|
|
value="",
|
|
interactive=True,
|
|
allow_custom_value=True,
|
|
)
|
|
create_refresh_button(
|
|
images_dir,
|
|
lambda: None,
|
|
lambda: {"choices": list_images_dirs(current_images_dir)},
|
|
"open_folder_small",
|
|
)
|
|
folder_button = gr.Button(
|
|
"📂",
|
|
elem_id="open_folder_small",
|
|
elem_classes=["tool"],
|
|
visible=(not headless),
|
|
)
|
|
folder_button.click(
|
|
get_folder_path,
|
|
outputs=images_dir,
|
|
show_progress=False,
|
|
)
|
|
load_images_button = gr.Button("Load", elem_id="open_folder")
|
|
caption_ext = gr.Dropdown(
|
|
label="Caption file extension",
|
|
choices=[".cap", ".caption", ".txt"],
|
|
value=".txt",
|
|
interactive=True,
|
|
allow_custom_value=True,
|
|
)
|
|
auto_save = gr.Checkbox(
|
|
label="Autosave", info="Options", value=True, interactive=True
|
|
)
|
|
|
|
images_dir.change(
|
|
fn=lambda path: gr.Dropdown(choices=[""] + list_images_dirs(path)),
|
|
inputs=images_dir,
|
|
outputs=images_dir,
|
|
show_progress=False,
|
|
)
|
|
|
|
|
|
with gr.Group(), gr.Row():
|
|
quick_tags_text = gr.Textbox(
|
|
label="Quick Tags",
|
|
placeholder="Comma separated list of tags",
|
|
interactive=True,
|
|
)
|
|
import_tags_button = gr.Button("Import", elem_id="open_folder")
|
|
ignore_load_tags_word_count = gr.Slider(
|
|
minimum=1,
|
|
maximum=100,
|
|
value=3,
|
|
step=1,
|
|
label="Ignore Imported Tags Above Word Count",
|
|
interactive=True,
|
|
)
|
|
|
|
|
|
def render_pagination():
|
|
gr.Button("< Prev", elem_id="open_folder").click(
|
|
paginate,
|
|
inputs=[page, max_page, gr.Number(value=-1, visible=False)],
|
|
outputs=[page],
|
|
)
|
|
page_count = gr.Label("Page 1", label="Page")
|
|
page_goto_text = gr.Textbox(
|
|
label="Goto page",
|
|
placeholder="Page Number",
|
|
interactive=True,
|
|
)
|
|
gr.Button("Go >", elem_id="open_folder").click(
|
|
paginate_go,
|
|
inputs=[page_goto_text, max_page],
|
|
outputs=[page],
|
|
)
|
|
gr.Button("Next >", elem_id="open_folder").click(
|
|
paginate,
|
|
inputs=[page, max_page, gr.Number(value=1, visible=False)],
|
|
outputs=[page],
|
|
)
|
|
return page_count
|
|
|
|
with gr.Row(visible=False) as pagination_row1:
|
|
page_count1 = render_pagination()
|
|
|
|
|
|
image_rows = []
|
|
image_files = []
|
|
image_images = []
|
|
image_caption_texts = []
|
|
image_tag_checks = []
|
|
save_buttons = []
|
|
for _ in range(IMAGES_TO_SHOW):
|
|
with gr.Row(visible=False) as row:
|
|
image_file = gr.Text(visible=False)
|
|
image_files.append(image_file)
|
|
image_image = gr.Image(type="filepath")
|
|
image_images.append(image_image)
|
|
image_caption_text = gr.TextArea(
|
|
label="Captions",
|
|
placeholder="Input captions",
|
|
interactive=True,
|
|
)
|
|
image_caption_texts.append(image_caption_text)
|
|
tag_checkboxes = gr.CheckboxGroup([], label="Tags", interactive=True)
|
|
save_button = gr.Button(
|
|
"💾",
|
|
elem_id="open_folder_small",
|
|
elem_classes=["tool"],
|
|
visible=False,
|
|
)
|
|
save_buttons.append(save_button)
|
|
|
|
|
|
image_caption_text.input(
|
|
update_image_caption,
|
|
inputs=[
|
|
quick_tags_text,
|
|
image_caption_text,
|
|
image_file,
|
|
loaded_images_dir,
|
|
caption_ext,
|
|
auto_save,
|
|
],
|
|
outputs=tag_checkboxes,
|
|
)
|
|
|
|
|
|
tag_checkboxes.input(
|
|
update_image_tags,
|
|
inputs=[
|
|
quick_tags_text,
|
|
tag_checkboxes,
|
|
image_file,
|
|
loaded_images_dir,
|
|
caption_ext,
|
|
auto_save,
|
|
],
|
|
outputs=[image_caption_text],
|
|
)
|
|
|
|
|
|
save_button.click(
|
|
save_caption,
|
|
inputs=[
|
|
image_caption_text,
|
|
caption_ext,
|
|
image_file,
|
|
images_dir,
|
|
],
|
|
)
|
|
|
|
image_tag_checks.append(tag_checkboxes)
|
|
image_rows.append(row)
|
|
|
|
|
|
with gr.Row(visible=False) as pagination_row2:
|
|
page_count2 = render_pagination()
|
|
|
|
|
|
quick_tags_text.change(
|
|
update_quick_tags,
|
|
inputs=[quick_tags_text] + image_caption_texts,
|
|
outputs=image_tag_checks,
|
|
)
|
|
|
|
|
|
import_tags_button.click(
|
|
import_tags_from_captions,
|
|
inputs=[
|
|
loaded_images_dir,
|
|
caption_ext,
|
|
quick_tags_text,
|
|
ignore_load_tags_word_count,
|
|
],
|
|
outputs=quick_tags_text,
|
|
)
|
|
|
|
|
|
load_images_button.click(
|
|
load_images,
|
|
inputs=[
|
|
images_dir,
|
|
caption_ext,
|
|
loaded_images_dir,
|
|
page,
|
|
max_page,
|
|
],
|
|
outputs=[loaded_images_dir, page, max_page],
|
|
)
|
|
|
|
|
|
|
|
|
|
image_update_key = gr.Text(visible=False)
|
|
image_update_key.change(
|
|
update_images,
|
|
inputs=[loaded_images_dir, caption_ext, quick_tags_text, page],
|
|
outputs=image_rows
|
|
+ image_files
|
|
+ image_images
|
|
+ image_caption_texts
|
|
+ image_tag_checks
|
|
+ [pagination_row1, pagination_row2],
|
|
show_progress=False,
|
|
)
|
|
|
|
listener_kwargs = {
|
|
"fn": lambda p, i: f"{p}-{i}",
|
|
"inputs": [page, loaded_images_dir],
|
|
"outputs": image_update_key,
|
|
}
|
|
page.change(**listener_kwargs)
|
|
loaded_images_dir.change(**listener_kwargs)
|
|
|
|
|
|
|
|
auto_save.change(
|
|
lambda auto_save: [gr.Button(visible=not auto_save)] * IMAGES_TO_SHOW,
|
|
inputs=auto_save,
|
|
outputs=save_buttons,
|
|
)
|
|
|
|
|
|
page.change(
|
|
lambda page, max_page: [f"Page {int(page)} / {int(max_page)}"] * 2,
|
|
inputs=[page, max_page],
|
|
outputs=[page_count1, page_count2],
|
|
show_progress=False,
|
|
)
|
|
|