MichaelDeutges commited on
Commit
2a08bd4
·
verified ·
1 Parent(s): e6bc5c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -62
app.py CHANGED
@@ -8,12 +8,10 @@ import pandas as pd
8
  from PIL import Image
9
  from filelock import FileLock
10
 
11
- from huggingface_hub import HfApi
12
-
13
-
14
  # ---------------- Config ----------------
 
15
  IMAGE_DIR = os.getenv("IMAGE_DIR", "images")
16
- LABELS_CSV = os.getenv("LABELS_CSV", "labels.csv")
17
  LABEL_LEFT = os.getenv("LABEL_A", "Class A")
18
  LABEL_RIGHT = os.getenv("LABEL_B", "Class B")
19
  SHOW_ALREADY_LABELED = os.getenv("SHOW_ALREADY_LABELED", "0") == "1"
@@ -21,29 +19,32 @@ SUPPORTED_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".we
21
 
22
  st.set_page_config(page_title="Two-Button Image Labeler", layout="centered")
23
 
 
24
  # --------------- Helpers ----------------
25
  def list_images():
26
- """Return sorted list of image filepaths inside IMAGE_DIR (recursively)."""
27
  paths = []
28
  for p in glob.glob(os.path.join(IMAGE_DIR, "**", "*"), recursive=True):
29
  if p.lower().endswith(SUPPORTED_EXTS):
30
  paths.append(p)
31
  return sorted(paths)
32
 
33
- def read_labels() -> pd.DataFrame:
 
34
  if os.path.exists(LABELS_CSV):
35
  try:
36
  return pd.read_csv(LABELS_CSV)
37
  except Exception:
38
- pass
39
  return pd.DataFrame(columns=["image", "label", "annotator", "timestamp"])
40
 
41
- def rel_to_image_dir(p: str) -> str:
 
42
  try:
43
  return str(Path(p).resolve().relative_to(Path(IMAGE_DIR).resolve()))
44
  except Exception:
45
  return p
46
 
 
47
  def write_label(image_path: str, label: str, annotator: str):
48
  os.makedirs(os.path.dirname(LABELS_CSV) or ".", exist_ok=True)
49
  record = {
@@ -52,58 +53,38 @@ def write_label(image_path: str, label: str, annotator: str):
52
  "annotator": annotator,
53
  "timestamp": datetime.now(timezone.utc).isoformat(),
54
  }
55
- # append locally (so the app logic can read it right away)
56
  with FileLock(LABELS_CSV + ".lock", timeout=10):
57
  exists = os.path.exists(LABELS_CSV)
58
  df = pd.DataFrame([record])
59
  df.to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
60
 
61
- # push to the Space repo so it appears in the Files tab
62
- token = os.getenv("HF_TOKEN")
63
- space_id = os.getenv("SPACE_ID")
64
- if token and space_id:
65
- try:
66
- api = HfApi(token=token)
67
- api.upload_file(
68
- path_or_fileobj=LABELS_CSV,
69
- path_in_repo=LABELS_CSV,
70
- repo_id=space_id,
71
- repo_type="space",
72
- commit_message=f"Add label: {record['image']} -> {record['label']} by {annotator}",
73
- )
74
- except Exception as e:
75
- # Non-fatal: still saved locally; just surface a hint
76
- st.toast(f"Saved locally; upload pending ({e})", icon="⚠️")
77
 
78
  # --------------- UI ---------------------
79
  st.title("🏷️ Two-Button Image Labeler")
80
- st.write("Enter your name, click **Start**, then label each image using the big buttons. "
81
- "Use **Skip** to bypass an image.")
 
 
82
 
83
- # ---- Sidebar controls (start + restart) ----
84
  with st.sidebar:
85
  default_name = st.session_state.get("annotator", "")
86
- annotator = st.text_input("Your name*", value=default_name, placeholder="e.g., Dr. Smith")
 
 
87
 
88
  c1, c2 = st.columns(2)
89
  with c1:
90
- start = st.button("Start", type="primary")
91
  with c2:
92
- restart_req = st.button("Restart")
93
 
94
  continue_by_name = st.toggle(
95
  "Only show images I haven't labeled yet",
96
  value=True,
97
  help="Continue where you left off, based on your name in labels.csv",
98
  )
99
- # Handle sidebar Restart (allows switching annotator anytime)
100
- if restart_req:
101
- for k in ["started", "order", "idx", "total"]:
102
- st.session_state.pop(k, None)
103
- st.session_state["annotator"] = annotator.strip()
104
- st.rerun()
105
 
106
- # Initialize session state
107
  if "order" not in st.session_state:
108
  st.session_state.order = []
109
  if "idx" not in st.session_state:
@@ -113,35 +94,45 @@ if "total" not in st.session_state:
113
  if "started" not in st.session_state:
114
  st.session_state.started = False
115
 
116
- # ---- Start pressed: build the queue for this annotator ----
 
 
 
 
 
 
 
117
  # Start flow
118
- if start:
119
  if not annotator.strip():
120
  st.sidebar.error("Please enter your name.")
121
  else:
 
122
  imgs = list_images()
123
- labels_df = st.session_state.labels_df
124
-
125
- # compute relative paths once
126
  rel_imgs = [rel_to_image_dir(p) for p in imgs]
127
 
128
  if continue_by_name:
129
- # show only images THIS annotator hasn't labeled yet
130
  done_by_me = set(
131
  labels_df.query("annotator == @annotator.strip()")["image"].astype(str)
132
  )
133
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in done_by_me]
134
 
135
  elif not SHOW_ALREADY_LABELED:
136
- # old behavior: hide anything labeled by anyone
137
  done_any = set(labels_df["image"].astype(str))
138
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in done_any]
139
 
140
  if not imgs:
141
  if continue_by_name:
142
- st.success(f"All caught up for **{annotator}** 🎉 Nothing left to label.")
 
 
143
  else:
144
- st.warning("No images found (or all labeled). Upload PNG/JPG/etc. to the `images/` folder.")
 
 
145
  else:
146
  st.session_state.order = imgs
147
  st.session_state.idx = 0
@@ -149,7 +140,7 @@ if start:
149
  st.session_state.started = True
150
  st.rerun()
151
 
152
- # ---- Main panel ----
153
  if not st.session_state.started:
154
  st.info("Fill your name on the left and press **Start**.")
155
  else:
@@ -157,43 +148,36 @@ else:
157
  total = st.session_state.total
158
  if idx >= total:
159
  st.success("All done 🎉 Thank you!")
160
- if st.button("🔄 Restart labeling"):
161
- for k in ["started", "order", "idx", "total"]:
162
- st.session_state.pop(k, None)
163
- st.rerun()
164
  else:
165
  current_image = st.session_state.order[idx]
166
- st.caption(f"Annotator: **{st.session_state.get('annotator','')}** — {idx} / {total}")
167
  try:
168
  img = Image.open(current_image)
169
- # If multipage TIFF, show first page
170
  if getattr(img, "n_frames", 1) > 1:
171
  img.seek(0)
172
- # Convert to RGB for browsers
173
  if img.mode not in ("RGB", "RGBA"):
174
  img = img.convert("RGB")
175
- st.image(img, use_container_width=True)
176
  except Exception as e:
177
  st.warning(f"Could not display image: {current_image}\n{e}")
178
 
179
  col1, col2, col3 = st.columns([1, 1, 1])
180
  with col1:
181
  if st.button(f"⬅️ {LABEL_LEFT}", use_container_width=True):
182
- write_label(current_image, LABEL_LEFT, st.session_state.get("annotator",""))
183
  st.session_state.idx += 1
184
  st.rerun()
185
  with col2:
186
  if st.button("Skip", use_container_width=True):
187
- write_label(current_image, "Skip", st.session_state.get("annotator",""))
188
  st.session_state.idx += 1
189
  st.rerun()
190
  with col3:
191
  if st.button(f"{LABEL_RIGHT} ➡️", use_container_width=True):
192
- write_label(current_image, LABEL_RIGHT, st.session_state.get("annotator",""))
193
  st.session_state.idx += 1
194
  st.rerun()
195
 
196
- # ---- Footer ----
197
  st.divider()
198
- st.caption("Labels are saved to `labels.csv` in this Space’s working directory. "
199
- "You’ll see it in the **Files** tab after the app writes to it.")
 
8
  from PIL import Image
9
  from filelock import FileLock
10
 
 
 
 
11
  # ---------------- Config ----------------
12
+ BASE_DIR = Path(__file__).parent.resolve()
13
  IMAGE_DIR = os.getenv("IMAGE_DIR", "images")
14
+ LABELS_CSV = os.getenv("LABELS_CSV", str(BASE_DIR / "labels.csv"))
15
  LABEL_LEFT = os.getenv("LABEL_A", "Class A")
16
  LABEL_RIGHT = os.getenv("LABEL_B", "Class B")
17
  SHOW_ALREADY_LABELED = os.getenv("SHOW_ALREADY_LABELED", "0") == "1"
 
19
 
20
  st.set_page_config(page_title="Two-Button Image Labeler", layout="centered")
21
 
22
+
23
  # --------------- Helpers ----------------
24
  def list_images():
 
25
  paths = []
26
  for p in glob.glob(os.path.join(IMAGE_DIR, "**", "*"), recursive=True):
27
  if p.lower().endswith(SUPPORTED_EXTS):
28
  paths.append(p)
29
  return sorted(paths)
30
 
31
+
32
+ def read_labels():
33
  if os.path.exists(LABELS_CSV):
34
  try:
35
  return pd.read_csv(LABELS_CSV)
36
  except Exception:
37
+ return pd.DataFrame(columns=["image", "label", "annotator", "timestamp"])
38
  return pd.DataFrame(columns=["image", "label", "annotator", "timestamp"])
39
 
40
+
41
+ def rel_to_image_dir(p: str):
42
  try:
43
  return str(Path(p).resolve().relative_to(Path(IMAGE_DIR).resolve()))
44
  except Exception:
45
  return p
46
 
47
+
48
  def write_label(image_path: str, label: str, annotator: str):
49
  os.makedirs(os.path.dirname(LABELS_CSV) or ".", exist_ok=True)
50
  record = {
 
53
  "annotator": annotator,
54
  "timestamp": datetime.now(timezone.utc).isoformat(),
55
  }
 
56
  with FileLock(LABELS_CSV + ".lock", timeout=10):
57
  exists = os.path.exists(LABELS_CSV)
58
  df = pd.DataFrame([record])
59
  df.to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  # --------------- UI ---------------------
63
  st.title("🏷️ Two-Button Image Labeler")
64
+ st.write(
65
+ "Enter your name, click **Start**, then label each image using the big buttons. "
66
+ "Use **Skip** to bypass an image."
67
+ )
68
 
 
69
  with st.sidebar:
70
  default_name = st.session_state.get("annotator", "")
71
+ annotator = st.text_input(
72
+ "Your name*", value=default_name, placeholder="e.g., Dr. Smith"
73
+ )
74
 
75
  c1, c2 = st.columns(2)
76
  with c1:
77
+ start_btn = st.button("Start", type="primary")
78
  with c2:
79
+ restart_btn = st.button("Restart")
80
 
81
  continue_by_name = st.toggle(
82
  "Only show images I haven't labeled yet",
83
  value=True,
84
  help="Continue where you left off, based on your name in labels.csv",
85
  )
 
 
 
 
 
 
86
 
87
+ # session state
88
  if "order" not in st.session_state:
89
  st.session_state.order = []
90
  if "idx" not in st.session_state:
 
94
  if "started" not in st.session_state:
95
  st.session_state.started = False
96
 
97
+ # Restart clears session
98
+ if restart_btn:
99
+ st.session_state.order = []
100
+ st.session_state.idx = 0
101
+ st.session_state.total = 0
102
+ st.session_state.started = False
103
+ st.rerun()
104
+
105
  # Start flow
106
+ if start_btn:
107
  if not annotator.strip():
108
  st.sidebar.error("Please enter your name.")
109
  else:
110
+ st.session_state["annotator"] = annotator.strip()
111
  imgs = list_images()
112
+ labels_df = read_labels()
 
 
113
  rel_imgs = [rel_to_image_dir(p) for p in imgs]
114
 
115
  if continue_by_name:
116
+ # only show images THIS annotator hasn't labeled yet
117
  done_by_me = set(
118
  labels_df.query("annotator == @annotator.strip()")["image"].astype(str)
119
  )
120
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in done_by_me]
121
 
122
  elif not SHOW_ALREADY_LABELED:
123
+ # hide anything labeled by anyone
124
  done_any = set(labels_df["image"].astype(str))
125
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in done_any]
126
 
127
  if not imgs:
128
  if continue_by_name:
129
+ st.success(
130
+ f"All caught up for **{annotator.strip()}** 🎉 Nothing left to label."
131
+ )
132
  else:
133
+ st.warning(
134
+ "No images found (or all labeled). Upload PNG/JPG/etc. to the `images/` folder."
135
+ )
136
  else:
137
  st.session_state.order = imgs
138
  st.session_state.idx = 0
 
140
  st.session_state.started = True
141
  st.rerun()
142
 
143
+ # main panel
144
  if not st.session_state.started:
145
  st.info("Fill your name on the left and press **Start**.")
146
  else:
 
148
  total = st.session_state.total
149
  if idx >= total:
150
  st.success("All done 🎉 Thank you!")
 
 
 
 
151
  else:
152
  current_image = st.session_state.order[idx]
153
+ st.caption(f"{idx} / {total}")
154
  try:
155
  img = Image.open(current_image)
 
156
  if getattr(img, "n_frames", 1) > 1:
157
  img.seek(0)
 
158
  if img.mode not in ("RGB", "RGBA"):
159
  img = img.convert("RGB")
160
+ st.image(img, use_column_width=True)
161
  except Exception as e:
162
  st.warning(f"Could not display image: {current_image}\n{e}")
163
 
164
  col1, col2, col3 = st.columns([1, 1, 1])
165
  with col1:
166
  if st.button(f"⬅️ {LABEL_LEFT}", use_container_width=True):
167
+ write_label(current_image, LABEL_LEFT, annotator.strip())
168
  st.session_state.idx += 1
169
  st.rerun()
170
  with col2:
171
  if st.button("Skip", use_container_width=True):
172
+ write_label(current_image, "Skip", annotator.strip())
173
  st.session_state.idx += 1
174
  st.rerun()
175
  with col3:
176
  if st.button(f"{LABEL_RIGHT} ➡️", use_container_width=True):
177
+ write_label(current_image, LABEL_RIGHT, annotator.strip())
178
  st.session_state.idx += 1
179
  st.rerun()
180
 
181
+ # Footer / admin
182
  st.divider()
183
+ st.caption("Labels are saved to `labels.csv` in this Space. You can download it from the Files tab.")