import gradio as gr
from huggingface_hub import HfApi, hf_hub_download, upload_file
import os
from PIL import Image
import random
# === CONFIGURATION ===
HF_TOKEN = os.environ.get("HF_TOKEN")
HF_IMAGE_REPO = "saleh-c4/arabic-ocr-images"
HF_LABEL_REPO = "saleh-c4/arabic-ocr-labels"
TEMP_DIR = "temp_downloads"
api = HfApi()
# === TRACKING STATE ===
all_images = [] # all line images with page
current_image = None
# === INITIALIZE IMAGE LIST ===
def fetch_all_images():
global all_images
files = api.list_repo_files(HF_IMAGE_REPO, repo_type="dataset", token=HF_TOKEN)
all_images = sorted([
f for f in files if f.endswith((".png", ".jpg", ".jpeg")) and "/" in f
])
fetch_all_images()
# === CHECK IF IMAGE ALREADY LABELED ===
def is_labeled(image_path):
filename = os.path.basename(image_path)
page = image_path.split("/")[0]
label_file = f"{page}.txt"
try:
label_path = hf_hub_download(
repo_id=HF_LABEL_REPO,
repo_type="dataset",
filename=label_file,
token=HF_TOKEN,
local_dir=TEMP_DIR,
local_dir_use_symlinks=False
)
with open(label_path, "r", encoding="utf-8") as f:
return any(filename in line for line in f)
except:
return False
# === LOAD A RANDOM UNLABELED IMAGE ===
def load_image():
global current_image
attempts = 0
while attempts < 20:
candidate = random.choice(all_images)
if not is_labeled(candidate):
current_image = candidate
break
attempts += 1
else:
return None, "", "✅ كل الصور مُعنونة!"
local_path = hf_hub_download(
repo_id=HF_IMAGE_REPO,
repo_type="dataset",
filename=current_image,
token=HF_TOKEN,
local_dir=TEMP_DIR,
local_dir_use_symlinks=False
)
img = Image.open(local_path)
status = f"{current_image} (لم تُعنون بعد)"
return img, "", status
# === HANDLE LABEL ===
def handle_label(text):
global current_image
if not current_image:
return load_image()
filename = os.path.basename(current_image)
page = current_image.split("/")[0]
label_file = f"{page}.txt"
local_label_path = f"{TEMP_DIR}/{label_file}"
try:
# Try downloading the existing label file
existing_lines = []
try:
label_path = hf_hub_download(
repo_id=HF_LABEL_REPO,
repo_type="dataset",
filename=label_file,
token=HF_TOKEN,
local_dir=TEMP_DIR,
local_dir_use_symlinks=False
)
with open(label_path, "r", encoding="utf-8") as f:
existing_lines = [line.strip() for line in f if line.strip()]
except:
pass # no existing file
# Add the new line
new_line = f"{filename}\t{text.strip()}"
existing_lines.append(new_line)
# Sort lines by filename
sorted_lines = sorted(existing_lines, key=lambda x: x.split("\t")[0])
# Save back
with open(local_label_path, "w", encoding="utf-8") as f:
f.write("\n".join(sorted_lines) + "\n")
upload_file(
path_or_fileobj=local_label_path,
path_in_repo=label_file,
repo_id=HF_LABEL_REPO,
repo_type="dataset",
token=HF_TOKEN
)
if os.path.exists(local_label_path):
os.remove(local_label_path)
print(f"✅ Labeled and sorted: {filename} → {label_file}")
except Exception as e:
print(f"❌ Failed to label: {e}")
return load_image()
# === UI ===
def setup():
img, _, desc = load_image()
return gr.update(value=img), gr.update(value="", placeholder="اكتب التسمية هنا..."), gr.update(value=desc)
with gr.Blocks(css="""
#label_image label {
display: none !important;
}
#label_image .absolute.top-0.right-0 {
display: none !important; /* Optional: hide buttons */
}
#label_image canvas,
#label_image img {
max-width: 90% !important;
margin-top: 20px;
margin-bottom: 10px; /* creates space from bottom buttons */
border-radius: 10px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
""") as demo:
gr.Markdown("🚨 ملاحظة: تسمية الصورة بـ 0 تعني أنها لا تحتوي على نص وسيتم حذفها من قاعدة البيانات.")
img = gr.Image(elem_id="label_image", container=False)
textbox = gr.Textbox(label="التسمية", rtl=True)
status = gr.Textbox(label="الوصف", interactive=False)
textbox.submit(fn=handle_label, inputs=textbox, outputs=[img, textbox, status])
demo.load(fn=setup, outputs=[img, textbox, status])
if __name__ == "__main__":
demo.launch()