MichaelDeutges commited on
Commit
a678911
·
verified ·
1 Parent(s): f578618

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -71
app.py CHANGED
@@ -10,23 +10,22 @@ from filelock import FileLock
10
  from huggingface_hub import HfApi
11
 
12
  # ---------------- Config ----------------
13
- IMAGE_DIR = os.getenv("IMAGE_DIR", "images")
14
-
15
- # Label-Namen zentral definieren
16
- LABEL_LEFT = os.getenv("LABEL_A", "NON-BLAST") # oder "NORMAL"
17
- LABEL_RIGHT = os.getenv("LABEL_B", "BLAST")
18
- LABEL_UNCERTAIN = os.getenv("LABEL_UNCERTAIN", "UNCERTAIN")
19
- LABEL_TRASH = os.getenv("LABEL_TRASH", "LOW_QUALITY") # alternativ "TRASH"
20
-
21
- LABELS_CSV = os.getenv("LABELS_CSV", "labels.csv")
22
  SUPPORTED_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp")
23
 
24
- HF_TOKEN = os.getenv("HF_TOKEN") # stored in Space secrets
25
- SPACE_ID = os.getenv("SPACE_ID", "MichaelDeutges/LabelingTest") # ggf. anpassen
 
 
 
26
 
 
 
 
27
  api = HfApi()
28
 
29
- st.set_page_config(page_title="Image Labeler", layout="centered")
30
 
31
  # --------------- Helpers ----------------
32
  def list_images():
@@ -51,6 +50,7 @@ def rel_to_image_dir(p: str):
51
  return p
52
 
53
  def write_label(image_path: str, label: str, annotator: str):
 
54
  os.makedirs(os.path.dirname(LABELS_CSV) or ".", exist_ok=True)
55
  record = {
56
  "image": rel_to_image_dir(image_path),
@@ -60,26 +60,31 @@ def write_label(image_path: str, label: str, annotator: str):
60
  }
61
  with FileLock(LABELS_CSV + ".lock", timeout=10):
62
  exists = os.path.exists(LABELS_CSV)
63
- df = pd.DataFrame([record])
64
- df.to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
65
 
66
- # Upload to Hugging Face repo (persistentes Files-Tab)
67
- try:
68
- api.upload_file(
69
- path_or_fileobj=LABELS_CSV,
70
- path_in_repo="labels.csv",
71
- repo_id=SPACE_ID,
72
- repo_type="space",
73
- token=HF_TOKEN
74
- )
75
- except Exception as e:
76
- print("Upload failed:", e)
 
77
 
78
  # --------------- UI ---------------------
79
- st.title("🏷️ Image Labeler")
80
- st.write("Name eingeben → **Start** → pro Bild Button klicken. **Skip** überspringt ohne Label (kommt später wieder).")
 
 
 
 
81
 
82
  with st.sidebar:
 
83
  default_name = st.session_state.get("annotator", "")
84
  annotator = st.text_input("Your name*", value=default_name, placeholder="e.g., Dr. Smith")
85
 
@@ -92,25 +97,18 @@ with st.sidebar:
92
  continue_by_name = st.toggle(
93
  "Only show images I haven't labeled yet",
94
  value=True,
95
- help="Zeigt nur Bilder, die DU noch nicht gelabelt hast (Skip zählt NICHT als gelabelt).",
96
  )
97
 
98
  # session state
99
- if "order" not in st.session_state:
100
- st.session_state.order = []
101
- if "idx" not in st.session_state:
102
- st.session_state.idx = 0
103
- if "total" not in st.session_state:
104
- st.session_state.total = 0
105
- if "started" not in st.session_state:
106
- st.session_state.started = False
107
 
108
  # reset
109
  if reset_btn:
110
- st.session_state.started = False
111
- st.session_state.order = []
112
- st.session_state.idx = 0
113
- st.session_state.total = 0
114
  st.rerun()
115
 
116
  # start
@@ -123,20 +121,16 @@ if start_btn:
123
  labels_df = read_labels()
124
 
125
  if continue_by_name and len(labels_df) > 0:
126
- # Nur als "erledigt" zählen: alles außer Skip
127
  already = set(
128
- labels_df.query("annotator == @annotator and label != 'Skip'")["image"].astype(str).tolist()
129
  )
130
  rel_imgs = [rel_to_image_dir(p) for p in imgs]
131
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in already]
132
 
133
  if not imgs:
134
- st.warning("No images found (oder für dich alles erledigt).")
135
  else:
136
- st.session_state.order = imgs
137
- st.session_state.idx = 0
138
- st.session_state.total = len(imgs)
139
- st.session_state.started = True
140
 
141
  # main panel
142
  if not st.session_state.started:
@@ -149,6 +143,16 @@ else:
149
  else:
150
  current_image = st.session_state.order[idx]
151
  st.caption(f"{idx+1} / {total}")
 
 
 
 
 
 
 
 
 
 
152
  try:
153
  img = Image.open(current_image)
154
  if getattr(img, "n_frames", 1) > 1:
@@ -159,37 +163,27 @@ else:
159
  except Exception as e:
160
  st.warning(f"Could not display image: {current_image}\n{e}")
161
 
162
- # Zeile 1: die beiden Hauptklassen
163
- c1, c2, c3 = st.columns([1, 1, 1])
164
- with c1:
165
- if st.button(f"⬅️ {LABEL_LEFT}", use_container_width=True):
166
- write_label(current_image, LABEL_LEFT, annotator.strip())
167
- st.session_state.idx += 1
168
- st.rerun()
169
- with c2:
170
- if st.button("Skip", use_container_width=True):
171
- # Skip wird gespeichert, zählt aber NICHT als erledigt beim Continue-Filter
172
- write_label(current_image, "Skip", annotator.strip())
173
- st.session_state.idx += 1
174
- st.rerun()
175
- with c3:
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
- # Zeile 2: zusätzliche Qualitäts-/Unsicherheits-Klassen
182
- c4, c5 = st.columns([1, 1])
183
- with c4:
184
- if st.button(LABEL_UNCERTAIN, use_container_width=True):
185
  write_label(current_image, LABEL_UNCERTAIN, annotator.strip())
186
  st.session_state.idx += 1
187
  st.rerun()
188
- with c5:
189
- if st.button(LABEL_TRASH, use_container_width=True):
190
- write_label(current_image, LABEL_TRASH, annotator.strip())
 
191
  st.session_state.idx += 1
192
  st.rerun()
193
 
194
  st.divider()
195
- st.caption("Labels are saved to `labels.csv` and synced to this Space.")
 
 
10
  from huggingface_hub import HfApi
11
 
12
  # ---------------- Config ----------------
13
+ IMAGE_DIR = os.getenv("IMAGE_DIR", "images")
14
+ LABELS_CSV = os.getenv("LABELS_CSV", "labels.csv")
 
 
 
 
 
 
 
15
  SUPPORTED_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp")
16
 
17
+ # Label names
18
+ LABEL_NONBLAST = os.getenv("LABEL_NONBLAST", "NON-BLAST")
19
+ LABEL_BLAST = os.getenv("LABEL_BLAST", "BLAST")
20
+ LABEL_UNCERTAIN = os.getenv("LABEL_UNCERTAIN", "UNCERTAIN")
21
+ LABEL_TRASH = os.getenv("LABEL_TRASH", "LOW_QUALITY")
22
 
23
+ # (Optional) Hugging Face sync
24
+ HF_TOKEN = os.getenv("HF_TOKEN") # set as a Secret in Space settings
25
+ SPACE_ID = os.getenv("SPACE_ID", "MichaelDeutges/LabelingTest") # set to your Space id
26
  api = HfApi()
27
 
28
+ st.set_page_config(page_title="Two-Button Image Labeler", layout="centered")
29
 
30
  # --------------- Helpers ----------------
31
  def list_images():
 
50
  return p
51
 
52
  def write_label(image_path: str, label: str, annotator: str):
53
+ """Append one row to labels.csv and (optionally) push to the Space repo."""
54
  os.makedirs(os.path.dirname(LABELS_CSV) or ".", exist_ok=True)
55
  record = {
56
  "image": rel_to_image_dir(image_path),
 
60
  }
61
  with FileLock(LABELS_CSV + ".lock", timeout=10):
62
  exists = os.path.exists(LABELS_CSV)
63
+ pd.DataFrame([record]).to_csv(LABELS_CSV, mode="a", header=not exists, index=False)
 
64
 
65
+ # best-effort sync to Hugging Face repo (safe to skip if no token/space id)
66
+ if HF_TOKEN and SPACE_ID:
67
+ try:
68
+ api.upload_file(
69
+ path_or_fileobj=LABELS_CSV,
70
+ path_in_repo="labels.csv",
71
+ repo_id=SPACE_ID,
72
+ repo_type="space",
73
+ token=HF_TOKEN
74
+ )
75
+ except Exception as e:
76
+ print("Upload failed:", e)
77
 
78
  # --------------- UI ---------------------
79
+ st.title("🏷️ Two-Button Image Labeler")
80
+ st.write(
81
+ "Enter your name, click **Start**, then classify each image. "
82
+ "Use **UNCERTAIN** if you’re not sure. "
83
+ "Use the 🗑️ icon for **LOW_QUALITY** images."
84
+ )
85
 
86
  with st.sidebar:
87
+ # remember last annotator
88
  default_name = st.session_state.get("annotator", "")
89
  annotator = st.text_input("Your name*", value=default_name, placeholder="e.g., Dr. Smith")
90
 
 
97
  continue_by_name = st.toggle(
98
  "Only show images I haven't labeled yet",
99
  value=True,
100
+ help="Continue where you left off, based on your name in labels.csv",
101
  )
102
 
103
  # session state
104
+ st.session_state.setdefault("order", [])
105
+ st.session_state.setdefault("idx", 0)
106
+ st.session_state.setdefault("total", 0)
107
+ st.session_state.setdefault("started", False)
 
 
 
 
108
 
109
  # reset
110
  if reset_btn:
111
+ st.session_state.update({"started": False, "order": [], "idx": 0, "total": 0})
 
 
 
112
  st.rerun()
113
 
114
  # start
 
121
  labels_df = read_labels()
122
 
123
  if continue_by_name and len(labels_df) > 0:
 
124
  already = set(
125
+ labels_df.query("annotator == @annotator")["image"].astype(str).tolist()
126
  )
127
  rel_imgs = [rel_to_image_dir(p) for p in imgs]
128
  imgs = [p for p, r in zip(imgs, rel_imgs) if r not in already]
129
 
130
  if not imgs:
131
+ st.warning("No images found (or all labeled). Upload to the `images/` folder.")
132
  else:
133
+ st.session_state.update({"order": imgs, "idx": 0, "total": len(imgs), "started": True})
 
 
 
134
 
135
  # main panel
136
  if not st.session_state.started:
 
143
  else:
144
  current_image = st.session_state.order[idx]
145
  st.caption(f"{idx+1} / {total}")
146
+
147
+ # top-right trash
148
+ spacer, trash_col = st.columns([9, 1])
149
+ with trash_col:
150
+ if st.button("🗑️", help=f"Mark as {LABEL_TRASH}", use_container_width=True):
151
+ write_label(current_image, LABEL_TRASH, annotator.strip())
152
+ st.session_state.idx += 1
153
+ st.rerun()
154
+
155
+ # image
156
  try:
157
  img = Image.open(current_image)
158
  if getattr(img, "n_frames", 1) > 1:
 
163
  except Exception as e:
164
  st.warning(f"Could not display image: {current_image}\n{e}")
165
 
166
+ # main classification row: NON-BLAST | UNCERTAIN | BLAST
167
+ c_left, c_mid, c_right = st.columns([1, 1, 1])
168
+
169
+ with c_left:
170
+ if st.button(f"⬅️ {LABEL_NONBLAST}", use_container_width=True):
171
+ write_label(current_image, LABEL_NONBLAST, annotator.strip())
 
 
 
 
 
 
 
 
 
 
172
  st.session_state.idx += 1
173
  st.rerun()
174
 
175
+ with c_mid:
176
+ if st.button(f"❓ {LABEL_UNCERTAIN}", use_container_width=True):
 
 
177
  write_label(current_image, LABEL_UNCERTAIN, annotator.strip())
178
  st.session_state.idx += 1
179
  st.rerun()
180
+
181
+ with c_right:
182
+ if st.button(f"{LABEL_BLAST} ➡️", use_container_width=True):
183
+ write_label(current_image, LABEL_BLAST, annotator.strip())
184
  st.session_state.idx += 1
185
  st.rerun()
186
 
187
  st.divider()
188
+ st.caption("Labels are saved to `labels.csv` (and synced to this Space if HF_TOKEN is set).")
189
+