""" Image Similarity Rating App ---------------------------- Reads pairs.csv (committed to the Space repo) and shows all pairs in random order to each user. No repetitions within a session. """ import io import os import uuid import random import pandas as pd import gradio as gr from datetime import datetime from datasets import Dataset from huggingface_hub import HfApi # ── Config ──────────────────────────────────────────────────────────────────── HF_TOKEN = os.environ.get("HF_TOKEN", "") HF_DATASET_REPO = os.environ.get("HF_DATASET_REPO", "") # where votes are saved CSV_PATH = "pairs.csv" # committed to Space repo # ── Load pairs CSV once at startup ─────────────────────────────────────────── print("Loading pairs.csv ...") _pairs_df = pd.read_csv(CSV_PATH) _pairs = _pairs_df.to_dict(orient="records") print(f"Loaded {len(_pairs)} pairs.") # ── Persistence ─────────────────────────────────────────────────────────────── VOTES_FILE = "votes.parquet" # single file in the results repo def save_votes_to_hub(votes: list[dict]): """ Append this session's votes to a single votes.parquet in the results repo. Strategy: download existing file -> concat -> upload back. """ if not HF_DATASET_REPO or not HF_TOKEN: print("HF_DATASET_REPO or HF_TOKEN not set -- votes not saved remotely.") return try: api = HfApi(token=HF_TOKEN) new_df = pd.DataFrame(votes) # Try to download the existing parquet and append try: existing_path = api.hf_hub_download( repo_id=HF_DATASET_REPO, repo_type="dataset", filename=VOTES_FILE, ) existing_df = pd.read_parquet(existing_path) combined_df = pd.concat([existing_df, new_df], ignore_index=True) except Exception: # File doesn't exist yet -- first run combined_df = new_df buf = io.BytesIO() combined_df.to_parquet(buf, index=False) buf.seek(0) api.upload_file( path_or_fileobj=buf, path_in_repo=VOTES_FILE, repo_id=HF_DATASET_REPO, repo_type="dataset", ) print(f"Appended {len(votes)} votes to {HF_DATASET_REPO}/{VOTES_FILE} " f"(total rows: {len(combined_df)})") except Exception as ex: print(f"Failed to save votes: {ex}") # ── CSS ─────────────────────────────────────────────────────────────────────── CSS = """ /* ── Main Container & Light Background ── */ body, .gradio-container { background: #ffffff !important; color: #000000 !important; } /* ── Instructions Box ── */ .instructions { background: #f9f9f9; border: 1px solid #ddd; border-radius: 8px; padding: 16px 20px; margin-bottom: 16px; font-size: 0.88rem; line-height: 1.6; color: #444; } .instructions strong { color: #000; } /* ── Scale Values (Your specific request) ── */ .scale-list li { color: #555; display: flex; align-items: center; gap: 10px; font-size: 0.85rem; } .scale-val { background: #ffffff !important; color: #000000 !important; border: 1px solid #ddd; /* Added a light border so white-on-white is visible */ border-radius: 4px; padding: 2px 8px; font-weight: 700; font-size: 0.8rem; min-width: 36px; text-align: center; flex-shrink: 0; } /* ── Progress Bar ── */ .progress-wrap { margin-bottom: 16px; } .progress-label { font-size: 0.8rem; color: #888; margin-bottom: 4px; text-align: right; } .progress-track { height: 4px; border-radius: 4px; overflow: hidden; background: #eee; } .progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s ease; background: #000; } /* ── Header ── */ h1 { font-size: 1.5rem; font-weight: 700; margin: 28px 0 4px; text-align: center; color: #000; } .subtitle { text-align: center; color: #888; margin-bottom: 20px; font-size: 0.9rem; } /* ── Done Banner ── */ .done-banner { background: #fdfdfd; border: 1px solid #bbb; border-radius: 8px; padding: 64px 24px; text-align: center; margin: 32px 0; } .done-icon { font-size: 3.5rem; margin-bottom: 16px; } .done-banner h2 { font-size: 1.8rem; margin: 0 0 12px; color: #000; } .done-banner p { margin: 0; font-size: 0.95rem; line-height: 1.7; color: #555; } footer { display: none !important; } """ # ── App ─────────────────────────────────────────────────────────────────────── def make_app(): with gr.Blocks(css=CSS, title="Image Similarity Rating") as demo: # State user_id_state = gr.State(lambda: str(uuid.uuid4())) queue_state = gr.State([]) index_state = gr.State(0) votes_state = gr.State([]) total_state = gr.State(0) # Header gr.HTML("
Rate how similar Image B is to Image A.
") # Done banner — hidden until all pairs are rated done_html = gr.HTML(visible=False) # Rating UI — hidden when done with gr.Column(visible=True) as rating_col: # Progress progress_html = gr.HTML() # Images with gr.Row(equal_height=True): img_left = gr.Image(label="Image A — Original", show_label=True, interactive=False, height=520) img_right = gr.Image(label="Image B — Generated", show_label=True, interactive=False, height=520) # Instructions gr.HTML("""