goktug14 commited on
Commit
7cd17b3
·
1 Parent(s): ac3810c

annotation and statistics

Browse files
Files changed (1) hide show
  1. app.py +104 -64
app.py CHANGED
@@ -76,8 +76,24 @@ results = [] # list of {'image': fname, 'labels': '...'}
76
  annotations = {} # {img_idx: [bool, ...]} aligned to ALL_LABELS
77
  label_counts = {lbl: 0 for lbl in ALL_LABELS} # used in statistics mode
78
 
 
79
  # ---------------------------
80
- # Core Functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  # ---------------------------
82
  def display_image(idx):
83
  """Displays the image at the given index and its saved annotations."""
@@ -104,59 +120,55 @@ def submit(*selections):
104
  if not images:
105
  return "No image to label", None
106
 
107
- # Save selections to our annotations dictionary
108
  annotations[current_index] = list(selections)
109
  fname = os.path.basename(images[current_index])
110
  chosen_labels = [lbl for lbl, sel in zip(ALL_LABELS, selections) if sel]
111
 
112
  global results
113
- # Remove any previous entry for this image to avoid duplicates
114
- results = [r for r in results if r['image'] != fname]
115
  results.append({'image': fname, 'labels': ', '.join(chosen_labels)})
116
 
117
- # Write the updated results to a CSV file
118
  df = pd.DataFrame(results)
119
  df.to_csv('image_labels.csv', index=False)
120
 
121
  return "Labels saved!", 'image_labels.csv'
122
 
123
  def upload_images(files):
124
- """Handles image uploads, resetting the application state."""
 
 
 
 
 
125
  global images, current_index, results, annotations
126
  if not files:
127
  return [None, "No images uploaded"] + [False] * len(ALL_LABELS) + [
128
- gr.update(visible=True), # image_upload stays visible if nothing came
129
- gr.update(visible=False), # csv_upload hidden
130
- gr.update(visible=False) # stats_csv_upload hidden
131
  ]
132
 
133
- # Gradio File returns temp files with .name path
134
  images = [f.name for f in files]
135
  current_index = 0
136
  results = []
137
  annotations = {}
138
  outputs = display_image(0)
139
 
140
- # Hide the image uploader after a successful upload; show per-image CSV upload
141
  return outputs + [
142
- gr.update(visible=False), # image_upload
143
- gr.update(visible=True), # csv_upload
144
- gr.update(visible=False) # stats_csv_upload
145
  ]
146
 
147
  def load_annotations(csv_file):
148
  """Loads annotations from an uploaded CSV file (annotation mode)."""
149
  global annotations, results
150
  if csv_file is None or not images:
151
- # If no CSV is uploaded or no images are loaded, do nothing.
152
  return display_image(current_index)
153
 
154
  try:
155
  df = pd.read_csv(csv_file.name)
156
- # Create a quick lookup map from filename to its index in the `images` list
157
  image_map = {os.path.basename(name): i for i, name in enumerate(images)}
158
 
159
- # Reset existing annotations and results
160
  annotations = {}
161
  results = df.to_dict('records')
162
 
@@ -164,23 +176,22 @@ def load_annotations(csv_file):
164
  fname = row.get('image', '')
165
  if fname in image_map:
166
  img_idx = image_map[fname]
167
- # Handle cases where labels might be empty (NaN)
168
  if pd.notna(row.get('labels', None)):
169
  saved_labels = set(l.strip() for l in str(row['labels']).split(',') if l.strip())
170
  else:
171
  saved_labels = set()
172
- # Create the boolean state list for the checkboxes
173
  states = [label in saved_labels for label in ALL_LABELS]
174
  annotations[img_idx] = states
175
  except Exception as e:
176
  print(f"Error loading annotations: {e}")
177
- # In case of error, just refresh the current view without changes
178
  return display_image(current_index)
179
 
180
- # After loading, refresh the view to show the annotations for the current image
181
  return display_image(current_index)
182
 
183
- # ---------- Statistics Mode Helpers ----------
 
 
 
184
  def aggregate_label_counts(files):
185
  """Read multiple CSVs of annotations and aggregate per-label counts."""
186
  counts = {lbl: 0 for lbl in ALL_LABELS}
@@ -207,7 +218,6 @@ def aggregate_label_counts(files):
207
  for item in items:
208
  if item in counts:
209
  counts[item] += 1
210
- # silently ignore labels not in ALL_LABELS
211
 
212
  return counts, file_ct, rows_ct
213
 
@@ -216,7 +226,6 @@ def upload_stats_csvs(files):
216
  global label_counts
217
  label_counts, file_ct, rows_ct = aggregate_label_counts(files)
218
 
219
- # Create updates for every checkbox: label "(count)", disabled & unchecked
220
  checkbox_updates = [
221
  gr.update(label=f"{lbl} ({label_counts.get(lbl, 0)})", value=False, interactive=False)
222
  for lbl in ALL_LABELS
@@ -224,76 +233,90 @@ def upload_stats_csvs(files):
224
  note = f"Statistics mode: loaded {file_ct} file(s), counted {rows_ct} annotation row(s)."
225
  return checkbox_updates + [note]
226
 
 
 
 
 
227
  def make_checkbox_updates_for_mode(is_stats):
228
  """Return a list of gr.update for all checkboxes depending on mode."""
229
  if is_stats:
230
- # Show counts & disable
231
  return [
232
  gr.update(label=f"{lbl} ({label_counts.get(lbl, 0)})", value=False, interactive=False)
233
  for lbl in ALL_LABELS
234
  ]
235
  else:
236
- # Restore original labels & interactivity; set values to current image's saved state
237
- states = annotations.get(current_index, [False] * len(ALL_LABELS))
238
  return [
239
- gr.update(label=lbl, value=val, interactive=True)
240
- for lbl, val in zip(ALL_LABELS, states)
241
  ]
242
 
243
  def toggle_mode(current_mode):
244
  """
245
- Toggle between 'annotate' and 'stats' modes.
246
  Returns updates for:
247
  - mode_state
248
- - image_upload (visible)
249
- - csv_upload (visible)
250
- - stats_csv_upload (visible)
251
- - img (visible)
252
- - caption (visible)
253
  - prev_btn (visible)
254
  - next_btn (visible)
255
  - submit_btn (interactive)
256
- - csv_downloader (visible)
257
  - status (value)
258
  - all checkboxes (labels/value/interactive)
259
  """
260
  new_mode = 'stats' if current_mode == 'annotate' else 'annotate'
261
  is_stats = (new_mode == 'stats')
262
 
263
- # Visibility & interactivity updates
 
 
 
 
 
 
 
 
 
 
264
  img_vis = not is_stats
265
  nav_vis = not is_stats
266
  submit_interactive = not is_stats
267
  downloader_vis = not is_stats
268
 
269
- # Uploaders
270
  image_upload_vis = not is_stats
271
- csv_upload_vis = not is_stats # allow annotation CSV in annotate mode
272
  stats_csv_upload_vis = is_stats
273
 
274
  status_text = (
275
  "Statistics mode: upload CSV files to compute per-label counts."
276
  if is_stats
277
- else "Annotation mode: select labels and submit. (Optional: load a CSV for existing annotations.)"
278
  )
279
 
280
  checkbox_updates = make_checkbox_updates_for_mode(is_stats)
281
 
 
282
  return (
283
  new_mode,
284
- gr.update(visible=image_upload_vis), # image_upload
285
- gr.update(visible=csv_upload_vis), # csv_upload
286
- gr.update(visible=stats_csv_upload_vis),# stats_csv_upload
287
- gr.update(visible=img_vis), # img
288
- gr.update(visible=img_vis), # caption
289
- gr.update(visible=nav_vis), # prev_btn
290
- gr.update(visible=nav_vis), # next_btn
291
- gr.update(interactive=submit_interactive), # submit_btn
292
- gr.update(visible=downloader_vis), # csv_downloader
293
- status_text, # status (value)
294
  *checkbox_updates
295
  )
296
 
 
297
  # ---------------------------
298
  # Gradio UI
299
  # ---------------------------
@@ -301,11 +324,29 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
301
  gr.Markdown("## Dermatology Annotation Tool")
302
 
303
  with gr.Row():
304
- image_upload = gr.File(label="1. Upload Images", file_count="multiple", file_types=["image"])
305
- # Annotation CSV (per-image) hidden until an image set is uploaded
306
- csv_upload = gr.File(label="2. (Optional) Upload Annotations CSV", file_types=[".csv"], visible=False)
307
- # Statistics CSV uploader (multi) — hidden by default; shown in stats mode
308
- stats_csv_upload = gr.File(label="Upload Annotation CSVs (Statistics Mode)", file_types=[".csv"], file_count="multiple", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  checkbox_components = []
311
 
@@ -357,31 +398,30 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
357
  next_btn = gr.Button("Next ➡️")
358
 
359
  with gr.Column(scale=1): # Controls and download column
360
- # --- TOGGLE BUTTON placed just above Submit Labels ---
361
  mode_state = gr.State("annotate") # 'annotate' or 'stats'
362
- toggle_btn = gr.Button("🔀 Switch to Statistics Mode", variant="secondary") # sits above Submit
 
363
  submit_btn = gr.Button("Submit Labels")
364
  status = gr.Label()
365
  csv_downloader = gr.File(label="Download labels CSV")
366
 
367
  # --- Event Handling ---
368
 
369
- # When images are uploaded (annotation mode)
370
  image_upload.upload(
371
  fn=upload_images,
372
  inputs=image_upload,
373
- outputs=[img, caption] + checkbox_components + [image_upload, csv_upload, stats_csv_upload]
374
  )
375
 
376
- # Load per-image annotations CSV (annotation mode)
377
  csv_upload.upload(
378
  fn=load_annotations,
379
  inputs=csv_upload,
380
  outputs=[img, caption] + checkbox_components
381
  )
382
 
383
- # Statistics CSVs upload (statistics mode)
384
- # Updates all checkbox labels with counts & disables them, also sets status text
385
  stats_csv_upload.upload(
386
  fn=upload_stats_csvs,
387
  inputs=stats_csv_upload,
@@ -405,11 +445,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
405
  outputs=[status, csv_downloader]
406
  )
407
 
408
- # Toggle mode (annotation <-> statistics)
409
  toggle_btn.click(
410
  fn=toggle_mode,
411
  inputs=[mode_state],
412
- outputs=[ # keep order in sync with toggle_mode return
413
  mode_state,
414
  image_upload, csv_upload, stats_csv_upload,
415
  img, caption,
 
76
  annotations = {} # {img_idx: [bool, ...]} aligned to ALL_LABELS
77
  label_counts = {lbl: 0 for lbl in ALL_LABELS} # used in statistics mode
78
 
79
+
80
  # ---------------------------
81
+ # Utilities to reset state
82
+ # ---------------------------
83
+ def reset_annotation_state():
84
+ global images, current_index, results, annotations
85
+ images = []
86
+ current_index = 0
87
+ results = []
88
+ annotations = {}
89
+
90
+ def reset_statistics_state():
91
+ global label_counts
92
+ label_counts = {lbl: 0 for lbl in ALL_LABELS}
93
+
94
+
95
+ # ---------------------------
96
+ # Core Functions (Annotation)
97
  # ---------------------------
98
  def display_image(idx):
99
  """Displays the image at the given index and its saved annotations."""
 
120
  if not images:
121
  return "No image to label", None
122
 
 
123
  annotations[current_index] = list(selections)
124
  fname = os.path.basename(images[current_index])
125
  chosen_labels = [lbl for lbl, sel in zip(ALL_LABELS, selections) if sel]
126
 
127
  global results
128
+ results = [r for r in results if r['image'] != fname] # dedupe this image
 
129
  results.append({'image': fname, 'labels': ', '.join(chosen_labels)})
130
 
 
131
  df = pd.DataFrame(results)
132
  df.to_csv('image_labels.csv', index=False)
133
 
134
  return "Labels saved!", 'image_labels.csv'
135
 
136
  def upload_images(files):
137
+ """
138
+ Handles image uploads, resetting the application state.
139
+ In Annotation mode's single Upload section: first upload IMAGES here.
140
+ After success, we hide the image uploader and reveal the CSV uploader
141
+ in the SAME section.
142
+ """
143
  global images, current_index, results, annotations
144
  if not files:
145
  return [None, "No images uploaded"] + [False] * len(ALL_LABELS) + [
146
+ gr.update(visible=True, value=None), # image_upload stays visible
147
+ gr.update(visible=False, value=None) # csv_upload hidden & cleared
 
148
  ]
149
 
 
150
  images = [f.name for f in files]
151
  current_index = 0
152
  results = []
153
  annotations = {}
154
  outputs = display_image(0)
155
 
156
+ # In the same "Upload" section: hide image uploader, show CSV uploader
157
  return outputs + [
158
+ gr.update(visible=False), # image_upload
159
+ gr.update(visible=True, value=None) # csv_upload
 
160
  ]
161
 
162
  def load_annotations(csv_file):
163
  """Loads annotations from an uploaded CSV file (annotation mode)."""
164
  global annotations, results
165
  if csv_file is None or not images:
 
166
  return display_image(current_index)
167
 
168
  try:
169
  df = pd.read_csv(csv_file.name)
 
170
  image_map = {os.path.basename(name): i for i, name in enumerate(images)}
171
 
 
172
  annotations = {}
173
  results = df.to_dict('records')
174
 
 
176
  fname = row.get('image', '')
177
  if fname in image_map:
178
  img_idx = image_map[fname]
 
179
  if pd.notna(row.get('labels', None)):
180
  saved_labels = set(l.strip() for l in str(row['labels']).split(',') if l.strip())
181
  else:
182
  saved_labels = set()
 
183
  states = [label in saved_labels for label in ALL_LABELS]
184
  annotations[img_idx] = states
185
  except Exception as e:
186
  print(f"Error loading annotations: {e}")
 
187
  return display_image(current_index)
188
 
 
189
  return display_image(current_index)
190
 
191
+
192
+ # ---------------------------
193
+ # Statistics Mode
194
+ # ---------------------------
195
  def aggregate_label_counts(files):
196
  """Read multiple CSVs of annotations and aggregate per-label counts."""
197
  counts = {lbl: 0 for lbl in ALL_LABELS}
 
218
  for item in items:
219
  if item in counts:
220
  counts[item] += 1
 
221
 
222
  return counts, file_ct, rows_ct
223
 
 
226
  global label_counts
227
  label_counts, file_ct, rows_ct = aggregate_label_counts(files)
228
 
 
229
  checkbox_updates = [
230
  gr.update(label=f"{lbl} ({label_counts.get(lbl, 0)})", value=False, interactive=False)
231
  for lbl in ALL_LABELS
 
233
  note = f"Statistics mode: loaded {file_ct} file(s), counted {rows_ct} annotation row(s)."
234
  return checkbox_updates + [note]
235
 
236
+
237
+ # ---------------------------
238
+ # Mode Toggle (discard state)
239
+ # ---------------------------
240
  def make_checkbox_updates_for_mode(is_stats):
241
  """Return a list of gr.update for all checkboxes depending on mode."""
242
  if is_stats:
243
+ # stats: counts shown (initially 0), disabled & unchecked
244
  return [
245
  gr.update(label=f"{lbl} ({label_counts.get(lbl, 0)})", value=False, interactive=False)
246
  for lbl in ALL_LABELS
247
  ]
248
  else:
249
+ # annotate: original labels, unchecked, interactive
 
250
  return [
251
+ gr.update(label=lbl, value=False, interactive=True)
252
+ for lbl in ALL_LABELS
253
  ]
254
 
255
  def toggle_mode(current_mode):
256
  """
257
+ Switches modes and DISCARD previous mode's data fully.
258
  Returns updates for:
259
  - mode_state
260
+ - image_upload (visible/value)
261
+ - csv_upload (visible/value)
262
+ - stats_csv_upload (visible/value)
263
+ - img (value/visible)
264
+ - caption (value/visible)
265
  - prev_btn (visible)
266
  - next_btn (visible)
267
  - submit_btn (interactive)
268
+ - csv_downloader (visible/value)
269
  - status (value)
270
  - all checkboxes (labels/value/interactive)
271
  """
272
  new_mode = 'stats' if current_mode == 'annotate' else 'annotate'
273
  is_stats = (new_mode == 'stats')
274
 
275
+ # DISCARD/RESET state from the mode we're leaving
276
+ if is_stats:
277
+ # leaving annotate -> stats
278
+ reset_annotation_state()
279
+ reset_statistics_state() # also reset counters to zero
280
+ else:
281
+ # leaving stats -> annotate
282
+ reset_statistics_state()
283
+ reset_annotation_state()
284
+
285
+ # Build UI updates
286
  img_vis = not is_stats
287
  nav_vis = not is_stats
288
  submit_interactive = not is_stats
289
  downloader_vis = not is_stats
290
 
 
291
  image_upload_vis = not is_stats
292
+ csv_upload_vis = False if is_stats else False # always hidden right after switch; shown only after images upload
293
  stats_csv_upload_vis = is_stats
294
 
295
  status_text = (
296
  "Statistics mode: upload CSV files to compute per-label counts."
297
  if is_stats
298
+ else "Annotation mode: first upload images, then upload the annotations CSV (same Upload section)."
299
  )
300
 
301
  checkbox_updates = make_checkbox_updates_for_mode(is_stats)
302
 
303
+ # Clear image/caption and any downloader file; clear uploaders' values
304
  return (
305
  new_mode,
306
+ gr.update(visible=image_upload_vis, value=None), # image_upload
307
+ gr.update(visible=csv_upload_vis, value=None), # csv_upload (hidden immediately on switch)
308
+ gr.update(visible=stats_csv_upload_vis, value=None),# stats_csv_upload
309
+ gr.update(value=None, visible=img_vis), # img
310
+ gr.update(value="No images uploaded", visible=img_vis), # caption
311
+ gr.update(visible=nav_vis), # prev_btn
312
+ gr.update(visible=nav_vis), # next_btn
313
+ gr.update(interactive=submit_interactive), # submit_btn
314
+ gr.update(visible=downloader_vis, value=None), # csv_downloader
315
+ status_text, # status (value)
316
  *checkbox_updates
317
  )
318
 
319
+
320
  # ---------------------------
321
  # Gradio UI
322
  # ---------------------------
 
324
  gr.Markdown("## Dermatology Annotation Tool")
325
 
326
  with gr.Row():
327
+ # ONE SINGLE 'Upload' SECTION AREA for Annotation mode;
328
+ # in Stats mode, this area is replaced by the stats uploader.
329
+ with gr.Group():
330
+ gr.Markdown("### Upload")
331
+ image_upload = gr.File(
332
+ label="1) Upload Images",
333
+ file_count="multiple",
334
+ file_types=["image"],
335
+ visible=True
336
+ )
337
+ # Same section: revealed after images are uploaded
338
+ csv_upload = gr.File(
339
+ label="2) (Optional) Upload Annotations CSV",
340
+ file_types=[".csv"],
341
+ visible=False
342
+ )
343
+ # Same section location but only for statistics mode
344
+ stats_csv_upload = gr.File(
345
+ label="Upload Annotation CSVs (Statistics Mode)",
346
+ file_types=[".csv"],
347
+ file_count="multiple",
348
+ visible=False
349
+ )
350
 
351
  checkbox_components = []
352
 
 
398
  next_btn = gr.Button("Next ➡️")
399
 
400
  with gr.Column(scale=1): # Controls and download column
 
401
  mode_state = gr.State("annotate") # 'annotate' or 'stats'
402
+ # --- TOGGLE BUTTON placed just above Submit Labels ---
403
+ toggle_btn = gr.Button("Switch Mode", variant="secondary")
404
  submit_btn = gr.Button("Submit Labels")
405
  status = gr.Label()
406
  csv_downloader = gr.File(label="Download labels CSV")
407
 
408
  # --- Event Handling ---
409
 
410
+ # When images are uploaded (annotation mode, same section)
411
  image_upload.upload(
412
  fn=upload_images,
413
  inputs=image_upload,
414
+ outputs=[img, caption] + checkbox_components + [image_upload, csv_upload]
415
  )
416
 
417
+ # Load per-image annotations CSV (annotation mode, same section)
418
  csv_upload.upload(
419
  fn=load_annotations,
420
  inputs=csv_upload,
421
  outputs=[img, caption] + checkbox_components
422
  )
423
 
424
+ # Statistics CSVs upload (statistics mode, replaces same section)
 
425
  stats_csv_upload.upload(
426
  fn=upload_stats_csvs,
427
  inputs=stats_csv_upload,
 
445
  outputs=[status, csv_downloader]
446
  )
447
 
448
+ # Toggle mode (ANNOTATE <-> STATS) and DISCARD state
449
  toggle_btn.click(
450
  fn=toggle_mode,
451
  inputs=[mode_state],
452
+ outputs=[
453
  mode_state,
454
  image_upload, csv_upload, stats_csv_upload,
455
  img, caption,