rushankg commited on
Commit
f443e9c
·
verified ·
1 Parent(s): 564db64

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +285 -0
app.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import datetime
4
+ import pandas as pd
5
+ import gradio as gr
6
+
7
+ ROENTGEN_DIR = "roentgen"
8
+ PEDISYNTH_DIR = "pedisynth"
9
+ RESULTS_DIR = "results"
10
+
11
+
12
+ def load_pairs():
13
+ """Load matching image pairs from both folders, keyed by filename."""
14
+ ro_files = {f for f in os.listdir(ROENTGEN_DIR)
15
+ if os.path.isfile(os.path.join(ROENTGEN_DIR, f))}
16
+ pe_files = {f for f in os.listdir(PEDISYNTH_DIR)
17
+ if os.path.isfile(os.path.join(PEDISYNTH_DIR, f))}
18
+
19
+ common = sorted(ro_files & pe_files)
20
+ pairs = []
21
+
22
+ for fname in common:
23
+ base, _ext = os.path.splitext(fname)
24
+ parts = base.split()
25
+ # "{condition} {number}" -> condition is everything except last token
26
+ condition = " ".join(parts[:-1]) if len(parts) > 1 else base
27
+
28
+ pairs.append(
29
+ {
30
+ "filename": fname,
31
+ "condition": condition,
32
+ "roentgen_path": os.path.join(ROENTGEN_DIR, fname),
33
+ "pedisynth_path": os.path.join(PEDISYNTH_DIR, fname),
34
+ }
35
+ )
36
+ return pairs
37
+
38
+
39
+ ALL_PAIRS = load_pairs()
40
+ os.makedirs(RESULTS_DIR, exist_ok=True)
41
+
42
+
43
+ def start_session(doctor_id):
44
+ """Initialize a new randomized session for this doctor."""
45
+ if not ALL_PAIRS:
46
+ status = "No matching image pairs found. Check your roentgen/ and pedisynth/ folders."
47
+ return (
48
+ None, # img_a
49
+ None, # img_b
50
+ "", # condition text
51
+ "", # progress text
52
+ None, # state
53
+ None, # results file
54
+ status,
55
+ gr.update(value=None), # clear choice
56
+ )
57
+
58
+ if not doctor_id:
59
+ doctor_id = "anonymous"
60
+
61
+ # Randomize order of pairs
62
+ pairs = ALL_PAIRS.copy()
63
+ random.shuffle(pairs)
64
+
65
+ # Randomize which model appears on left (A) vs right (B)
66
+ session_pairs = []
67
+ for p in pairs:
68
+ if random.random() < 0.5:
69
+ left_model = "roentgen"
70
+ right_model = "pedisynth"
71
+ left_path = p["roentgen_path"]
72
+ right_path = p["pedisynth_path"]
73
+ else:
74
+ left_model = "pedisynth"
75
+ right_model = "roentgen"
76
+ left_path = p["pedisynth_path"]
77
+ right_path = p["roentgen_path"]
78
+
79
+ session_pairs.append(
80
+ {
81
+ **p,
82
+ "left_model": left_model,
83
+ "right_model": right_model,
84
+ "left_path": left_path,
85
+ "right_path": right_path,
86
+ }
87
+ )
88
+
89
+ state = {
90
+ "doctor_id": doctor_id,
91
+ "pairs": session_pairs,
92
+ "idx": 0,
93
+ "records": [],
94
+ }
95
+
96
+ first = session_pairs[0]
97
+ condition_text = f"**Condition:** {first['condition']}"
98
+ progress_text = f"Pair 1 of {len(session_pairs)}"
99
+ status = f"Session started for doctor: {doctor_id}"
100
+
101
+ return (
102
+ first["left_path"],
103
+ first["right_path"],
104
+ condition_text,
105
+ progress_text,
106
+ state,
107
+ None, # no CSV yet
108
+ status,
109
+ gr.update(value=None), # clear choice
110
+ )
111
+
112
+
113
+ def next_pair(choice, state):
114
+ """Record the doctor's choice and move to the next pair."""
115
+ # If no active session
116
+ if state is None or not state.get("pairs"):
117
+ status = "No active session. Please start a session first."
118
+ return (
119
+ None,
120
+ None,
121
+ "",
122
+ "",
123
+ state,
124
+ None,
125
+ status,
126
+ gr.update(value=None),
127
+ )
128
+
129
+ idx = state["idx"]
130
+ pairs = state["pairs"]
131
+
132
+ # Ensure choice is made
133
+ if not choice:
134
+ status = "Please select A or B before proceeding."
135
+ cur_pair = pairs[idx]
136
+ condition_text = f"**Condition:** {cur_pair['condition']}"
137
+ progress_text = f"Pair {idx + 1} of {len(pairs)}"
138
+ return (
139
+ cur_pair["left_path"],
140
+ cur_pair["right_path"],
141
+ condition_text,
142
+ progress_text,
143
+ state,
144
+ None,
145
+ status,
146
+ gr.update(), # keep current choice
147
+ )
148
+
149
+ cur_pair = pairs[idx]
150
+
151
+ chosen_side = choice
152
+ chosen_model = cur_pair["left_model"] if choice == "A" else cur_pair["right_model"]
153
+
154
+ record = {
155
+ "doctor_id": state["doctor_id"],
156
+ "timestamp_utc": datetime.datetime.utcnow().isoformat(),
157
+ "pair_index": idx,
158
+ "filename": cur_pair["filename"],
159
+ "condition": cur_pair["condition"],
160
+ "left_model": cur_pair["left_model"],
161
+ "right_model": cur_pair["right_model"],
162
+ "left_image_path": cur_pair["left_path"],
163
+ "right_image_path": cur_pair["right_path"],
164
+ "chosen_side": chosen_side,
165
+ "chosen_model": chosen_model,
166
+ }
167
+ state["records"].append(record)
168
+
169
+ # Advance index
170
+ state["idx"] += 1
171
+ idx = state["idx"]
172
+
173
+ # If we've reached the end, save CSV
174
+ if idx >= len(pairs):
175
+ df = pd.DataFrame(state["records"])
176
+ ts = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")
177
+ safe_doctor_id = "".join(
178
+ c for c in state["doctor_id"] if c.isalnum() or c in ("_", "-")
179
+ )
180
+ csv_name = f"session_{safe_doctor_id}_{ts}.csv"
181
+ csv_path = os.path.join(RESULTS_DIR, csv_name)
182
+ df.to_csv(csv_path, index=False)
183
+
184
+ status = (
185
+ f"Session complete. Saved {len(state['records'])} comparisons "
186
+ f"to `{csv_name}`."
187
+ )
188
+
189
+ # Optionally, reset session index or leave state as-is for audit
190
+ return (
191
+ None, # img_a
192
+ None, # img_b
193
+ "Session finished.", # condition text
194
+ "", # progress
195
+ state,
196
+ csv_path, # file for download
197
+ status,
198
+ gr.update(value=None),
199
+ )
200
+
201
+ # Otherwise, show next pair
202
+ next_p = pairs[idx]
203
+ condition_text = f"**Condition:** {next_p['condition']}"
204
+ progress_text = f"Pair {idx + 1} of {len(pairs)}"
205
+ status = f"Recorded choice for pair {idx}."
206
+
207
+ return (
208
+ next_p["left_path"],
209
+ next_p["right_path"],
210
+ condition_text,
211
+ progress_text,
212
+ state,
213
+ None,
214
+ status,
215
+ gr.update(value=None), # clear choice
216
+ )
217
+
218
+
219
+ with gr.Blocks() as demo:
220
+ gr.Markdown("# Synthetic Chest X-ray A/B Evaluation")
221
+
222
+ with gr.Row():
223
+ doctor_id = gr.Textbox(
224
+ label="Doctor ID (or initials)",
225
+ placeholder="e.g., dr_smith",
226
+ )
227
+ start_button = gr.Button("Start New Session", variant="primary")
228
+
229
+ state = gr.State()
230
+
231
+ with gr.Row():
232
+ condition_text = gr.Markdown("")
233
+ progress_text = gr.Markdown("")
234
+
235
+ with gr.Row():
236
+ img_a = gr.Image(label="Image A", interactive=False)
237
+ img_b = gr.Image(label="Image B", interactive=False)
238
+
239
+ choice = gr.Radio(
240
+ choices=["A", "B"],
241
+ label="Which image better matches the stated condition?",
242
+ interactive=True,
243
+ )
244
+
245
+ next_button = gr.Button("Next")
246
+
247
+ results_file = gr.File(
248
+ label="Download session CSV",
249
+ interactive=False,
250
+ )
251
+ status_text = gr.Markdown("")
252
+
253
+ # Wire callbacks
254
+ start_button.click(
255
+ fn=start_session,
256
+ inputs=doctor_id,
257
+ outputs=[
258
+ img_a,
259
+ img_b,
260
+ condition_text,
261
+ progress_text,
262
+ state,
263
+ results_file,
264
+ status_text,
265
+ choice,
266
+ ],
267
+ )
268
+
269
+ next_button.click(
270
+ fn=next_pair,
271
+ inputs=[choice, state],
272
+ outputs=[
273
+ img_a,
274
+ img_b,
275
+ condition_text,
276
+ progress_text,
277
+ state,
278
+ results_file,
279
+ status_text,
280
+ choice,
281
+ ],
282
+ )
283
+
284
+ if __name__ == "__main__":
285
+ demo.launch()