MichaelDeutges commited on
Commit
e73a793
·
verified ·
1 Parent(s): 28620bb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -0
app.py CHANGED
@@ -3,6 +3,179 @@ import glob
3
  from pathlib import Path
4
  from datetime import datetime, timezone
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import streamlit as st
7
  import pandas as pd
8
  from PIL import Image
@@ -181,3 +354,4 @@ else:
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.")
 
 
3
  from pathlib import Path
4
  from datetime import datetime, timezone
5
 
6
+ 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:
106
+ st.session_state.idx = 0
107
+ if "total" not in st.session_state:
108
+ st.session_state.total = 0
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:
120
+ labeled = set(labels_df["image"].astype(str).tolist())
121
+ rel_imgs = [rel_to_image_dir(p) for p in imgs]
122
+ imgs = [p for p, r in zip(imgs, rel_imgs) if r not in labeled]
123
+
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:
136
+ idx = st.session_state.idx
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)
150
+ except Exception as e:
151
+ st.warning(f"Could not display image: {current_image}\n{e}")
152
+
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).")
172
+
173
+ '''
174
+ import os
175
+ import glob
176
+ from pathlib import Path
177
+ from datetime import datetime, timezone
178
+
179
  import streamlit as st
180
  import pandas as pd
181
  from PIL import Image
 
354
  # Footer / admin
355
  st.divider()
356
  st.caption("Labels are saved to `labels.csv` in this Space. You can download it from the Files tab.")
357
+ '''