ahmedtaha100 commited on
Commit
e3dafe3
·
verified ·
1 Parent(s): 03ee85c

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +101 -26
app.py CHANGED
@@ -2,8 +2,10 @@ from __future__ import annotations
2
 
3
  import csv
4
  import hashlib
 
5
  import json
6
  import random
 
7
  import time
8
  from pathlib import Path
9
 
@@ -30,6 +32,75 @@ FIELDNAMES = [
30
  USE_GITHUB = github_configured()
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def load_pairs() -> list[dict]:
34
  if USE_GITHUB:
35
  import github_storage
@@ -45,7 +116,7 @@ def load_pairs() -> list[dict]:
45
 
46
  def get_results_path(reviewer_id: str) -> Path:
47
  RESULTS_DIR.mkdir(parents=True, exist_ok=True)
48
- safe_id = hashlib.sha256(reviewer_id.encode()).hexdigest()[:12]
49
  return RESULTS_DIR / f"reviews_{safe_id}.csv"
50
 
51
 
@@ -73,10 +144,7 @@ def save_review(reviewer_id: str, pair_index: int, review: dict) -> None:
73
  existing[pair_index] = review
74
 
75
  with open(path, "w", newline="") as f:
76
- writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
77
- writer.writeheader()
78
- for idx in sorted(existing.keys()):
79
- writer.writerow(existing[idx])
80
 
81
  append_audit_log(reviewer_id, pair_index, "update" if is_update else "submit")
82
 
@@ -121,19 +189,25 @@ def render_login() -> str | None:
121
  "the best of your clinical judgment."
122
  )
123
  st.markdown("---")
124
- reviewer_id = st.text_input("Enter your reviewer ID to begin")
125
- if reviewer_id.strip():
126
- return reviewer_id.strip()
 
 
 
127
  return None
128
 
129
 
130
- def render_image(path: str | Path, label: str) -> None:
131
  st.subheader(label)
132
- p = Path(path) if not isinstance(path, Path) else path
133
- if p.exists():
134
- st.image(str(p), use_container_width=True)
 
 
 
135
  else:
136
- st.warning(f"Image not found: {p.name}")
137
 
138
 
139
  def main() -> None:
@@ -164,16 +238,13 @@ def main() -> None:
164
  st.sidebar.markdown(f"**Reviewer:** {reviewer_id}")
165
  st.sidebar.metric("Progress", f"{reviewed} / {total}")
166
  st.sidebar.progress(reviewed / total if total > 0 else 0)
167
-
168
- if not USE_GITHUB:
169
- path = get_results_path(reviewer_id)
170
- if path.exists():
171
- st.sidebar.download_button(
172
- "Export Results CSV",
173
- data=path.read_text(),
174
- file_name=f"validation_{reviewer_id}.csv",
175
- mime="text/csv",
176
- )
177
 
178
  if st.sidebar.button("Log out"):
179
  append_audit_log(reviewer_id, -1, "logout")
@@ -223,7 +294,7 @@ def main() -> None:
223
  existing = reviews.get(pair_index, {})
224
 
225
  plausible = st.radio(
226
- "1. Is the counterfactual clinically plausible as a real spine X-ray?",
227
  ["Yes", "No"],
228
  index=0 if existing.get("clinically_plausible", "Yes") == "Yes" else 1,
229
  horizontal=True,
@@ -231,7 +302,7 @@ def main() -> None:
231
  )
232
 
233
  preserved = st.radio(
234
- "2. Is the pathology preserved across both images?",
235
  ["Yes", "No", "Uncertain"],
236
  index=["Yes", "No", "Uncertain"].index(
237
  existing.get("pathology_preserved", "Yes")
@@ -266,7 +337,11 @@ def main() -> None:
266
  "comments": comments,
267
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
268
  }
269
- save_review(reviewer_id, pair_index, review)
 
 
 
 
270
 
271
  if pos < total - 1:
272
  st.session_state.position = pos + 1
 
2
 
3
  import csv
4
  import hashlib
5
+ import io
6
  import json
7
  import random
8
+ import re
9
  import time
10
  from pathlib import Path
11
 
 
32
  USE_GITHUB = github_configured()
33
 
34
 
35
+ def _load_reviewer_tokens() -> dict[str, str]:
36
+ import os
37
+ tokens: dict[str, str] = {}
38
+ for key, val in os.environ.items():
39
+ if key.startswith("REVIEWER_TOKEN_"):
40
+ reviewer_id = key.replace("REVIEWER_TOKEN_", "").lower()
41
+ tokens[val] = reviewer_id
42
+ try:
43
+ reviewer_secrets = st.secrets.get("reviewers", {})
44
+ for reviewer_id, token in reviewer_secrets.items():
45
+ tokens[str(token)] = str(reviewer_id)
46
+ except (KeyError, FileNotFoundError):
47
+ pass
48
+ return tokens
49
+
50
+
51
+ def _authenticate(token: str) -> str | None:
52
+ valid_tokens = _load_reviewer_tokens()
53
+ if not valid_tokens:
54
+ if re.match(r"^[a-zA-Z0-9_-]{2,30}$", token):
55
+ return token
56
+ return None
57
+ return valid_tokens.get(token)
58
+
59
+
60
+ def _safe_reviewer_id(reviewer_id: str) -> str:
61
+ return hashlib.sha256(reviewer_id.encode()).hexdigest()[:12]
62
+
63
+
64
+ def _safe_filename_id(value: str) -> str:
65
+ safe = re.sub(r"[^a-zA-Z0-9_-]+", "_", value).strip("_")
66
+ return safe or _safe_reviewer_id(value)
67
+
68
+
69
+ def _reviews_to_csv(reviews: dict[int, dict]) -> str:
70
+ output = io.StringIO()
71
+ writer = csv.DictWriter(output, fieldnames=FIELDNAMES, extrasaction="ignore")
72
+ writer.writeheader()
73
+ for idx in sorted(reviews.keys()):
74
+ writer.writerow(reviews[idx])
75
+ return output.getvalue()
76
+
77
+
78
+ def _strip_dicom_metadata(file_path: Path) -> bytes | None:
79
+ suffix = file_path.suffix.lower()
80
+ if suffix in (".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"):
81
+ return file_path.read_bytes()
82
+ if suffix in (".dcm", ".dicom"):
83
+ try:
84
+ import pydicom
85
+ from PIL import Image
86
+
87
+ ds = pydicom.dcmread(str(file_path))
88
+ pixel_array = ds.pixel_data
89
+ if hasattr(ds, "pixel_array"):
90
+ pixel_array = ds.pixel_array
91
+ else:
92
+ return None
93
+ img = Image.fromarray(pixel_array)
94
+ if img.mode not in ("L", "RGB"):
95
+ img = img.convert("L")
96
+ buf = io.BytesIO()
97
+ img.save(buf, format="PNG")
98
+ return buf.getvalue()
99
+ except Exception:
100
+ return None
101
+ return file_path.read_bytes() if file_path.exists() else None
102
+
103
+
104
  def load_pairs() -> list[dict]:
105
  if USE_GITHUB:
106
  import github_storage
 
116
 
117
  def get_results_path(reviewer_id: str) -> Path:
118
  RESULTS_DIR.mkdir(parents=True, exist_ok=True)
119
+ safe_id = _safe_reviewer_id(reviewer_id)
120
  return RESULTS_DIR / f"reviews_{safe_id}.csv"
121
 
122
 
 
144
  existing[pair_index] = review
145
 
146
  with open(path, "w", newline="") as f:
147
+ f.write(_reviews_to_csv(existing))
 
 
 
148
 
149
  append_audit_log(reviewer_id, pair_index, "update" if is_update else "submit")
150
 
 
189
  "the best of your clinical judgment."
190
  )
191
  st.markdown("---")
192
+ token = st.text_input("Enter your access token to begin", type="password")
193
+ if token.strip():
194
+ reviewer_id = _authenticate(token.strip())
195
+ if reviewer_id:
196
+ return reviewer_id
197
+ st.error("Invalid access token.")
198
  return None
199
 
200
 
201
+ def render_image(path: Path, label: str) -> None:
202
  st.subheader(label)
203
+ if not path.exists():
204
+ st.warning(f"Image not found: {path.name}")
205
+ return
206
+ image_bytes = _strip_dicom_metadata(path)
207
+ if image_bytes:
208
+ st.image(image_bytes, use_container_width=True)
209
  else:
210
+ st.warning(f"Could not load image: {path.name}")
211
 
212
 
213
  def main() -> None:
 
238
  st.sidebar.markdown(f"**Reviewer:** {reviewer_id}")
239
  st.sidebar.metric("Progress", f"{reviewed} / {total}")
240
  st.sidebar.progress(reviewed / total if total > 0 else 0)
241
+ st.sidebar.download_button(
242
+ "Export Results CSV",
243
+ data=_reviews_to_csv(reviews),
244
+ file_name=f"validation_{_safe_filename_id(reviewer_id)}.csv",
245
+ mime="text/csv",
246
+ disabled=reviewed == 0,
247
+ )
 
 
 
248
 
249
  if st.sidebar.button("Log out"):
250
  append_audit_log(reviewer_id, -1, "logout")
 
294
  existing = reviews.get(pair_index, {})
295
 
296
  plausible = st.radio(
297
+ "1. Do both images appear clinically plausible as real spine X-rays?",
298
  ["Yes", "No"],
299
  index=0 if existing.get("clinically_plausible", "Yes") == "Yes" else 1,
300
  horizontal=True,
 
302
  )
303
 
304
  preserved = st.radio(
305
+ "2. Is the pathology consistent across both images?",
306
  ["Yes", "No", "Uncertain"],
307
  index=["Yes", "No", "Uncertain"].index(
308
  existing.get("pathology_preserved", "Yes")
 
337
  "comments": comments,
338
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
339
  }
340
+ try:
341
+ save_review(reviewer_id, pair_index, review)
342
+ except Exception as e:
343
+ st.error(f"Failed to save review: {e}. Please try again.")
344
+ return
345
 
346
  if pos < total - 1:
347
  st.session_state.position = pos + 1