rushankg commited on
Commit
c697283
·
verified ·
1 Parent(s): 5f6ec5d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +241 -279
app.py CHANGED
@@ -1,285 +1,247 @@
 
 
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()
 
1
+ import streamlit as st
2
+ import pandas as pd
3
  import os
4
+ from pathlib import Path
5
+ from datetime import datetime
6
  import random
7
+ from PIL import Image
8
+ import io
9
+
10
+ # Configure page
11
+ st.set_page_config(
12
+ page_title="X-Ray Quality Evaluation",
13
+ page_icon="🏥",
14
+ layout="wide"
15
+ )
16
+
17
+ # Initialize session state
18
+ if "session_active" not in st.session_state:
19
+ st.session_state.session_active = False
20
+ if "image_pairs" not in st.session_state:
21
+ st.session_state.image_pairs = []
22
+ if "current_index" not in st.session_state:
23
+ st.session_state.current_index = 0
24
+ if "evaluations" not in st.session_state:
25
+ st.session_state.evaluations = []
26
+ if "doctor_name" not in st.session_state:
27
+ st.session_state.doctor_name = ""
28
+
29
+ # Helper functions
30
+ def get_image_files(folder_path):
31
+ """Get all image files from a folder."""
32
+ valid_extensions = {'.jpg', '.jpeg', '.png', '.gif'}
33
+ files = {}
34
+ if os.path.exists(folder_path):
35
+ for file in os.listdir(folder_path):
36
+ if Path(file).suffix.lower() in valid_extensions:
37
+ # Extract condition from filename (everything except the last number)
38
+ parts = file.rsplit(' ', 1)
39
+ condition = parts[0] if len(parts) > 1 else file
40
+ if condition not in files:
41
+ files[condition] = []
42
+ files[condition].append(file)
43
+ return files
44
+
45
+ def create_image_pairs():
46
+ """Create paired images from roentgen and pedisynth folders."""
47
+ roentgen_files = get_image_files("roentgen")
48
+ pedisynth_files = get_image_files("pedisynth")
49
+
50
  pairs = []
51
+
52
+ # Match files by condition across folders
53
+ for condition in roentgen_files:
54
+ if condition in pedisynth_files:
55
+ for roent_file in roentgen_files[condition]:
56
+ for pedi_file in pedisynth_files[condition]:
57
+ # Check if they have the same base name
58
+ roent_base = Path(roent_file).stem
59
+ pedi_base = Path(pedi_file).stem
60
+ if roent_base == pedi_base:
61
+ # Randomly decide which image goes left and which goes right
62
+ is_roentgen_left = random.choice([True, False])
63
+ pairs.append({
64
+ 'roentgen': roent_file,
65
+ 'pedisynth': pedi_file,
66
+ 'condition': condition,
67
+ 'base_name': roent_base,
68
+ 'roentgen_left': is_roentgen_left
69
+ })
70
+
71
+ # Randomize order
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  random.shuffle(pairs)
73
+ return pairs
74
 
75
+ def load_image(folder, filename):
76
+ """Load image from folder."""
77
+ filepath = os.path.join(folder, filename)
78
+ try:
79
+ return Image.open(filepath)
80
+ except Exception as e:
81
+ st.error(f"Error loading image {filename}: {e}")
82
+ return None
83
+
84
+ def save_evaluations(evaluations, doctor_name):
85
+ """Save evaluations to CSV."""
86
+ df = pd.DataFrame(evaluations)
87
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
88
+ filename = f"evaluations_{doctor_name}_{timestamp}.csv"
89
+
90
+ # Save to outputs folder if it exists, otherwise current directory
91
+ output_dir = "outputs"
92
+ os.makedirs(output_dir, exist_ok=True)
93
+ filepath = os.path.join(output_dir, filename)
94
+
95
+ df.to_csv(filepath, index=False)
96
+ return filepath, filename
97
+
98
+ # Main UI
99
+ st.title("🏥 X-Ray Quality Evaluation System")
100
+ st.markdown("**Pairwise Preference Test for Synthetic X-Ray Images**")
101
+
102
+ # Sidebar - Session management
103
+ with st.sidebar:
104
+ st.header("Session Control")
105
+
106
+ if not st.session_state.session_active:
107
+ doctor_name = st.text_input("Enter your name:", key="doc_input")
108
+
109
+ if st.button("Start Session", type="primary"):
110
+ if doctor_name.strip():
111
+ st.session_state.doctor_name = doctor_name
112
+ st.session_state.image_pairs = create_image_pairs()
113
+ st.session_state.session_active = True
114
+ st.session_state.current_index = 0
115
+ st.session_state.evaluations = []
116
+ st.rerun()
117
+ else:
118
+ st.error("Please enter your name to start a session.")
119
+ else:
120
+ st.success(f"✓ Session active for: {st.session_state.doctor_name}")
121
+ progress = st.session_state.current_index / len(st.session_state.image_pairs)
122
+ st.progress(progress)
123
+ st.metric("Progress", f"{st.session_state.current_index}/{len(st.session_state.image_pairs)}")
124
+
125
+ if st.button("End Session & Download Results", type="primary"):
126
+ if st.session_state.evaluations:
127
+ filepath, filename = save_evaluations(
128
+ st.session_state.evaluations,
129
+ st.session_state.doctor_name
130
+ )
131
+ st.success(f"✓ Results saved: {filename}")
132
+
133
+ # Provide download link
134
+ with open(filepath, 'r') as f:
135
+ csv_data = f.read()
136
+ st.download_button(
137
+ label="Download CSV",
138
+ data=csv_data,
139
+ file_name=filename,
140
+ mime="text/csv"
141
+ )
142
+ st.session_state.session_active = False
143
+ st.rerun()
144
+ else:
145
+ st.warning("No evaluations recorded yet.")
146
+
147
+ # Main content
148
+ if not st.session_state.session_active:
149
+ st.info("👈 Enter your name in the sidebar and click 'Start Session' to begin.")
150
+ else:
151
+ if st.session_state.current_index >= len(st.session_state.image_pairs):
152
+ st.success("✅ Evaluation complete! All images have been reviewed.")
153
+ if st.button("End Session & Download Results"):
154
+ if st.session_state.evaluations:
155
+ filepath, filename = save_evaluations(
156
+ st.session_state.evaluations,
157
+ st.session_state.doctor_name
158
+ )
159
+ st.success(f"Results saved: {filename}")
160
+ with open(filepath, 'r') as f:
161
+ csv_data = f.read()
162
+ st.download_button(
163
+ label="Download CSV",
164
+ data=csv_data,
165
+ file_name=filename,
166
+ mime="text/csv"
167
+ )
168
+ st.session_state.session_active = False
169
+ st.rerun()
170
+ else:
171
+ current_pair = st.session_state.image_pairs[st.session_state.current_index]
172
+
173
+ st.subheading(f"Image {st.session_state.current_index + 1} of {len(st.session_state.image_pairs)}")
174
+ st.write(f"**Condition:** {current_pair['condition']}")
175
+
176
+ # Display images side by side (blinded - no labels about source)
177
+ col1, col2 = st.columns(2)
178
+
179
+ # Determine which image goes where
180
+ if current_pair['roentgen_left']:
181
+ left_image = load_image("roentgen", current_pair['roentgen'])
182
+ right_image = load_image("pedisynth", current_pair['pedisynth'])
183
+ left_source = "roentgen"
184
+ right_source = "pedisynth"
185
  else:
186
+ left_image = load_image("pedisynth", current_pair['pedisynth'])
187
+ right_image = load_image("roentgen", current_pair['roentgen'])
188
+ left_source = "pedisynth"
189
+ right_source = "roentgen"
190
+
191
+ with col1:
192
+ st.write("### Option A")
193
+ if left_image:
194
+ st.image(left_image, use_column_width=True)
195
+ else:
196
+ st.error("Could not load image A")
197
+
198
+ with col2:
199
+ st.write("### Option B")
200
+ if right_image:
201
+ st.image(right_image, use_column_width=True)
202
+ else:
203
+ st.error("Could not load image B")
204
+
205
+ # Preference selection
206
+ st.markdown("---")
207
+ st.write("**Which image is of better quality?**")
208
+
209
+ col_a, col_b, col_equal = st.columns(3)
210
+
211
+ with col_a:
212
+ if st.button("👈 Prefer Option A", use_container_width=True, key="btn_a"):
213
+ preferred_source = left_source
214
+ st.session_state.evaluations.append({
215
+ 'timestamp': datetime.now().isoformat(),
216
+ 'condition': current_pair['condition'],
217
+ 'base_name': current_pair['base_name'],
218
+ 'preference': preferred_source.capitalize(),
219
+ 'doctor': st.session_state.doctor_name
220
+ })
221
+ st.session_state.current_index += 1
222
+ st.rerun()
223
+
224
+ with col_b:
225
+ if st.button("👉 Prefer Option B", use_container_width=True, key="btn_b"):
226
+ preferred_source = right_source
227
+ st.session_state.evaluations.append({
228
+ 'timestamp': datetime.now().isoformat(),
229
+ 'condition': current_pair['condition'],
230
+ 'base_name': current_pair['base_name'],
231
+ 'preference': preferred_source.capitalize(),
232
+ 'doctor': st.session_state.doctor_name
233
+ })
234
+ st.session_state.current_index += 1
235
+ st.rerun()
236
+
237
+ with col_equal:
238
+ if st.button("↔️ Equal Quality", use_container_width=True, key="btn_equal"):
239
+ st.session_state.evaluations.append({
240
+ 'timestamp': datetime.now().isoformat(),
241
+ 'condition': current_pair['condition'],
242
+ 'base_name': current_pair['base_name'],
243
+ 'preference': 'Equal',
244
+ 'doctor': st.session_state.doctor_name
245
+ })
246
+ st.session_state.current_index += 1
247
+ st.rerun()