MichaelDeutges commited on
Commit
eb498ec
·
verified ·
1 Parent(s): a42883a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -74
app.py CHANGED
@@ -7,99 +7,77 @@ import streamlit as st
7
  import pandas as pd
8
  from PIL import Image
9
  from filelock import FileLock
10
- from huggingface_hub import HfApi
11
-
12
- # -------------------------------------------------------------------
13
- # Sichtbare/verlässliche Pfade: alles IM Repo-Ordner (/home/user/app)
14
- # -------------------------------------------------------------------
15
- BASE_DIR = Path(__file__).parent.resolve()
16
- IMAGE_DIR = Path(os.getenv("IMAGE_DIR", str(BASE_DIR / "images")))
17
- LABELS_CSV = Path(os.getenv("LABELS_CSV", str(BASE_DIR / "labels.csv")))
18
 
 
 
 
19
  LABEL_LEFT = os.getenv("LABEL_A", "Class A")
20
  LABEL_RIGHT = os.getenv("LABEL_B", "Class B")
21
  SHOW_ALREADY_LABELED = os.getenv("SHOW_ALREADY_LABELED", "0") == "1"
22
  SUPPORTED_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp")
23
 
24
- # Repo-ID für Uploads (nimmt SPACE_ID, sonst Fallback auf festen Namen)
25
- REPO_ID = os.environ.get("SPACE_ID", "MichaelDeutges/LabelingTest")
26
-
27
  st.set_page_config(page_title="Two-Button Image Labeler", layout="centered")
28
 
29
- # labels.csv anlegen (Header), damit sie sofort im Files-Tab auftaucht
30
- if not LABELS_CSV.exists():
31
- LABELS_CSV.write_text("image,label,annotator,timestamp\n", encoding="utf-8")
32
-
33
- # ----------------------- Helpers -----------------------------------
34
  def list_images():
35
- if not IMAGE_DIR.exists():
36
- return []
37
  paths = []
38
- for p in glob.glob(str(IMAGE_DIR / "**" / "*"), recursive=True):
39
  if p.lower().endswith(SUPPORTED_EXTS):
40
  paths.append(p)
41
  return sorted(paths)
42
 
43
- def read_labels():
44
- try:
45
- return pd.read_csv(LABELS_CSV) if LABELS_CSV.exists() else pd.DataFrame(
46
- columns=["image", "label", "annotator", "timestamp"]
47
- )
48
- except Exception:
49
- return pd.DataFrame(columns=["image", "label", "annotator", "timestamp"])
50
 
51
- def rel_to_image_dir(p: str):
52
  try:
53
- return str(Path(p).resolve().relative_to(IMAGE_DIR.resolve()))
54
  except Exception:
55
  return p
56
 
57
- def push_labels_to_repo():
58
- """Lädt labels.csv in das Space-Repo hoch (erscheint im Files-Tab)."""
59
- token = os.environ.get("HF_TOKEN")
60
- if not token:
61
- # kein Token gesetzt -> still und gut (App läuft trotzdem)
62
- return
63
- try:
64
- api = HfApi()
65
- api.upload_file(
66
- path_or_fileobj=str(LABELS_CSV),
67
- path_in_repo="labels.csv",
68
- repo_id=REPO_ID,
69
- repo_type="space",
70
- commit_message="Update labels.csv",
71
- token=token,
72
- )
73
- except Exception as e:
74
- print("push_labels_to_repo error:", e)
75
-
76
  def write_label(image_path: str, label: str, annotator: str):
77
- LABELS_CSV.parent.mkdir(parents=True, exist_ok=True)
 
78
  record = {
79
  "image": rel_to_image_dir(image_path),
80
  "label": label,
81
  "annotator": annotator,
82
- "timestamp": datetime.now(timezone.utc).isoformat(),
83
  }
84
- with FileLock(str(LABELS_CSV) + ".lock", timeout=10):
85
- exists = LABELS_CSV.exists()
86
- df = pd.DataFrame([record])
87
- df.to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
88
- # nach jedem Klick committen
89
- push_labels_to_repo()
90
-
91
- # ----------------------- UI ----------------------------------------
92
  st.title("🏷️ Two-Button Image Labeler")
93
- st.write(
94
- "Enter your name, click **Start**, then label each image using the big buttons. "
95
- "Use **Skip** to bypass an image."
96
- )
97
 
 
98
  with st.sidebar:
99
- annotator = st.text_input("Your name*", value="")
100
- start = st.button("Start", type="primary")
101
-
102
- # session state
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  if "order" not in st.session_state:
104
  st.session_state.order = []
105
  if "idx" not in st.session_state:
@@ -109,11 +87,13 @@ if "total" not in st.session_state:
109
  if "started" not in st.session_state:
110
  st.session_state.started = False
111
 
112
- # start logic
113
  if start:
114
  if not annotator.strip():
115
  st.sidebar.error("Please enter your name.")
116
  else:
 
 
117
  imgs = list_images()
118
  labels_df = read_labels()
119
  if not SHOW_ALREADY_LABELED and len(labels_df) > 0:
@@ -124,12 +104,13 @@ if start:
124
  if not imgs:
125
  st.warning("No images found (or all labeled). Upload PNG/JPG/etc. to the `images/` folder.")
126
  else:
127
- st.session_state.order = imgs # deterministische Reihenfolge
 
128
  st.session_state.idx = 0
129
  st.session_state.total = len(imgs)
130
  st.session_state.started = True
131
 
132
- # main area
133
  if not st.session_state.started:
134
  st.info("Fill your name on the left and press **Start**.")
135
  else:
@@ -137,13 +118,19 @@ else:
137
  total = st.session_state.total
138
  if idx >= total:
139
  st.success("All done 🎉 Thank you!")
 
 
 
 
140
  else:
141
  current_image = st.session_state.order[idx]
142
- st.caption(f"{idx} / {total}")
143
  try:
144
  img = Image.open(current_image)
145
- if getattr(img, "n_frames", 1) > 1: # multipage TIFF erste Seite
 
146
  img.seek(0)
 
147
  if img.mode not in ("RGB", "RGBA"):
148
  img = img.convert("RGB")
149
  st.image(img, use_column_width=True)
@@ -153,19 +140,21 @@ else:
153
  col1, col2, col3 = st.columns([1, 1, 1])
154
  with col1:
155
  if st.button(f"⬅️ {LABEL_LEFT}", use_container_width=True):
156
- write_label(current_image, LABEL_LEFT, annotator.strip())
157
  st.session_state.idx += 1
158
  st.rerun()
159
  with col2:
160
  if st.button("Skip", use_container_width=True):
161
- write_label(current_image, "Skip", annotator.strip())
162
  st.session_state.idx += 1
163
  st.rerun()
164
  with col3:
165
  if st.button(f"{LABEL_RIGHT} ➡️", use_container_width=True):
166
- write_label(current_image, LABEL_RIGHT, annotator.strip())
167
  st.session_state.idx += 1
168
  st.rerun()
169
 
 
170
  st.divider()
171
- st.caption("Labels are saved & committed as `labels.csv` to this Space (see the Files tab).")
 
 
7
  import pandas as pd
8
  from PIL import Image
9
  from filelock import FileLock
 
 
 
 
 
 
 
 
10
 
11
+ # ---------------- Config ----------------
12
+ IMAGE_DIR = os.getenv("IMAGE_DIR", "images")
13
+ LABELS_CSV = os.getenv("LABELS_CSV", "labels.csv")
14
  LABEL_LEFT = os.getenv("LABEL_A", "Class A")
15
  LABEL_RIGHT = os.getenv("LABEL_B", "Class B")
16
  SHOW_ALREADY_LABELED = os.getenv("SHOW_ALREADY_LABELED", "0") == "1"
17
  SUPPORTED_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp")
18
 
 
 
 
19
  st.set_page_config(page_title="Two-Button Image Labeler", layout="centered")
20
 
21
+ # --------------- Helpers ----------------
 
 
 
 
22
  def list_images():
23
+ """Return sorted list of image filepaths inside IMAGE_DIR (recursively)."""
 
24
  paths = []
25
+ for p in glob.glob(os.path.join(IMAGE_DIR, "**", "*"), recursive=True):
26
  if p.lower().endswith(SUPPORTED_EXTS):
27
  paths.append(p)
28
  return sorted(paths)
29
 
30
+ def read_labels() -> pd.DataFrame:
31
+ if os.path.exists(LABELS_CSV):
32
+ try:
33
+ return pd.read_csv(LABELS_CSV)
34
+ except Exception:
35
+ pass
36
+ return pd.DataFrame(columns=["image", "label", "annotator", "timestamp"])
37
 
38
+ def rel_to_image_dir(p: str) -> str:
39
  try:
40
+ return str(Path(p).resolve().relative_to(Path(IMAGE_DIR).resolve()))
41
  except Exception:
42
  return p
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def write_label(image_path: str, label: str, annotator: str):
45
+ """Append one row to labels.csv with a file lock so concurrent users are safe."""
46
+ os.makedirs(os.path.dirname(LABELS_CSV) or ".", exist_ok=True)
47
  record = {
48
  "image": rel_to_image_dir(image_path),
49
  "label": label,
50
  "annotator": annotator,
51
+ "timestamp": datetime.now(timezone.utc).isoformat()
52
  }
53
+ with FileLock(LABELS_CSV + ".lock", timeout=10):
54
+ exists = os.path.exists(LABELS_CSV)
55
+ pd.DataFrame([record]).to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
56
+
57
+ # --------------- UI ---------------------
 
 
 
58
  st.title("🏷️ Two-Button Image Labeler")
59
+ st.write("Enter your name, click **Start**, then label each image using the big buttons. "
60
+ "Use **Skip** to bypass an image.")
 
 
61
 
62
+ # ---- Sidebar controls (start + restart) ----
63
  with st.sidebar:
64
+ default_name = st.session_state.get("annotator", "")
65
+ annotator = st.text_input("Your name*", value=default_name, placeholder="e.g., Dr. Smith")
66
+
67
+ c1, c2 = st.columns(2)
68
+ with c1:
69
+ start = st.button("Start", type="primary")
70
+ with c2:
71
+ restart_req = st.button("Restart")
72
+
73
+ # Handle sidebar Restart (allows switching annotator anytime)
74
+ if restart_req:
75
+ for k in ["started", "order", "idx", "total"]:
76
+ st.session_state.pop(k, None)
77
+ st.session_state["annotator"] = annotator.strip()
78
+ st.rerun()
79
+
80
+ # Initialize session state
81
  if "order" not in st.session_state:
82
  st.session_state.order = []
83
  if "idx" not in st.session_state:
 
87
  if "started" not in st.session_state:
88
  st.session_state.started = False
89
 
90
+ # ---- Start pressed: build the queue for this annotator ----
91
  if start:
92
  if not annotator.strip():
93
  st.sidebar.error("Please enter your name.")
94
  else:
95
+ st.session_state["annotator"] = annotator.strip()
96
+
97
  imgs = list_images()
98
  labels_df = read_labels()
99
  if not SHOW_ALREADY_LABELED and len(labels_df) > 0:
 
104
  if not imgs:
105
  st.warning("No images found (or all labeled). Upload PNG/JPG/etc. to the `images/` folder.")
106
  else:
107
+ # Deterministic order (shuffle if you want random)
108
+ st.session_state.order = imgs
109
  st.session_state.idx = 0
110
  st.session_state.total = len(imgs)
111
  st.session_state.started = True
112
 
113
+ # ---- Main panel ----
114
  if not st.session_state.started:
115
  st.info("Fill your name on the left and press **Start**.")
116
  else:
 
118
  total = st.session_state.total
119
  if idx >= total:
120
  st.success("All done 🎉 Thank you!")
121
+ if st.button("🔄 Restart labeling"):
122
+ for k in ["started", "order", "idx", "total"]:
123
+ st.session_state.pop(k, None)
124
+ st.rerun()
125
  else:
126
  current_image = st.session_state.order[idx]
127
+ st.caption(f"Annotator: **{st.session_state.get('annotator','')}** — {idx} / {total}")
128
  try:
129
  img = Image.open(current_image)
130
+ # If multipage TIFF, show first page
131
+ if getattr(img, "n_frames", 1) > 1:
132
  img.seek(0)
133
+ # Convert to RGB for browsers
134
  if img.mode not in ("RGB", "RGBA"):
135
  img = img.convert("RGB")
136
  st.image(img, use_column_width=True)
 
140
  col1, col2, col3 = st.columns([1, 1, 1])
141
  with col1:
142
  if st.button(f"⬅️ {LABEL_LEFT}", use_container_width=True):
143
+ write_label(current_image, LABEL_LEFT, st.session_state.get("annotator",""))
144
  st.session_state.idx += 1
145
  st.rerun()
146
  with col2:
147
  if st.button("Skip", use_container_width=True):
148
+ write_label(current_image, "Skip", st.session_state.get("annotator",""))
149
  st.session_state.idx += 1
150
  st.rerun()
151
  with col3:
152
  if st.button(f"{LABEL_RIGHT} ➡️", use_container_width=True):
153
+ write_label(current_image, LABEL_RIGHT, st.session_state.get("annotator",""))
154
  st.session_state.idx += 1
155
  st.rerun()
156
 
157
+ # ---- Footer ----
158
  st.divider()
159
+ st.caption("Labels are saved to `labels.csv` in this Space’s working directory. "
160
+ "You’ll see it in the **Files** tab after the app writes to it.")