goktug14 commited on
Commit
25d9fb3
·
1 Parent(s): a200ce1

statistics mode

Browse files
Files changed (1) hide show
  1. app.py +126 -139
app.py CHANGED
@@ -2,55 +2,29 @@ import gradio as gr
2
  import pandas as pd
3
  from PIL import Image
4
  import os
 
 
 
5
 
6
  # Define your sections and labels
7
  SECTION_LABELS = {
8
  "Oil Pore Related Issues": [
9
- "Very Large Pores (Not Red)",
10
- "Whiteheads (Clogged Pores)",
11
- "Blackheads (Clogged Pores)",
12
- "Shinny Skin",
13
- "Sebaceous Filaments (Sebum)"
14
  ],
15
  "Acne and Blemishes": [
16
- "Pustules",
17
- "Papules",
18
- "Nodules",
19
- "Cysts",
20
- "Acne",
21
- "Rosacea",
22
- "Telangiectasia",
23
- "Milia",
24
- "Scars",
25
- "Ice Berg Scars",
26
- ],
27
- "Redness and Irritation": [
28
- "Redness",
29
- "Irritation",
30
- ],
31
- "Dryness and Texture Issues": [
32
- "Dryness",
33
- "Fine Lines / Wrinkles",
34
- "Skin Flakes"
35
- ],
36
- "Aging and Elasticity Issues": [
37
- "Loose Skin",
38
- "Deep Wrinkles"
39
- ],
40
- "Pigmentation Issues": [
41
- "Dark Spots",
42
- "Melasma",
43
- "Freckles"
44
  ],
 
 
 
 
45
  "Rosacea": [
46
- "Erythematous Telangiectasia Rosacea",
47
- "Papillopustular Rosacea",
48
- "Fimatoz Rosacea",
49
- "Ocular Rosacea"
50
  ],
51
- "Eczema": [
52
- "Seboreik Dermatit"
53
- ]
54
  }
55
 
56
  # Define the sections for each column to control UI layout
@@ -62,21 +36,20 @@ column4_sections = ["Rosacea", "Eczema"]
62
  # Combine all section lists to define the exact UI order
63
  UI_ORDERED_SECTIONS = column1_sections + column2_sections + column3_sections + column4_sections
64
 
65
- # Flattened labels list, created in the SAME order as the UI checkboxes will be. This is the fix.
66
  ALL_LABELS = [
67
  label
68
  for section_name in UI_ORDERED_SECTIONS
69
  for label in SECTION_LABELS.get(section_name, [])
70
  ]
71
 
72
-
73
- # Global state
74
  images = []
75
  current_index = 0
76
  results = []
77
  annotations = {}
78
 
79
- # Core functions
80
 
81
  def display_image(idx):
82
  """Displays the image at the given index and its saved annotations."""
@@ -90,7 +63,6 @@ def display_image(idx):
90
  return [img, caption] + states
91
  return [None, "No images uploaded"] + [False] * len(ALL_LABELS)
92
 
93
-
94
  def navigate(delta):
95
  """Navigates to the next or previous image."""
96
  global current_index
@@ -99,31 +71,23 @@ def navigate(delta):
99
  current_index = (current_index + delta + len(images)) % len(images)
100
  return display_image(current_index)
101
 
102
-
103
  def submit(*selections):
104
  """Saves the current annotations to the state and writes to a CSV file."""
105
  if not images:
106
  return "No image to label", None
107
-
108
- # Save selections to our annotations dictionary
109
  annotations[current_index] = list(selections)
110
  fname = os.path.basename(images[current_index])
111
  chosen_labels = [lbl for lbl, sel in zip(ALL_LABELS, selections) if sel]
112
 
113
  global results
114
- # Remove any previous entry for this image to avoid duplicates
115
  results = [r for r in results if r['image'] != fname]
116
  results.append({'image': fname, 'labels': ', '.join(chosen_labels)})
117
-
118
- # Write the updated results to a CSV file
119
  df = pd.DataFrame(results)
120
  df.to_csv('image_labels.csv', index=False)
121
-
122
  return "Labels saved!", 'image_labels.csv'
123
 
124
-
125
  def upload_images(files):
126
- """Handles image uploads, resetting the application state."""
127
  global images, current_index, results, annotations
128
  images = [f.name for f in files]
129
  current_index = 0
@@ -137,133 +101,156 @@ def load_annotations(csv_file):
137
  """Loads annotations from an uploaded CSV file."""
138
  global annotations, results
139
  if csv_file is None or not images:
140
- # If no CSV is uploaded or no images are loaded, do nothing.
141
  return display_image(current_index)
142
-
143
  try:
144
  df = pd.read_csv(csv_file.name)
145
- # Create a quick lookup map from filename to its index in the `images` list
146
  image_map = {os.path.basename(name): i for i, name in enumerate(images)}
147
-
148
- # Reset existing annotations and results
149
  annotations = {}
150
  results = df.to_dict('records')
151
-
152
  for _, row in df.iterrows():
153
  fname = row['image']
154
- # Check if the image from the CSV is in the currently uploaded images
155
  if fname in image_map:
156
  img_idx = image_map[fname]
157
- # Handle cases where labels might be empty (NaN)
158
- if pd.notna(row['labels']):
159
- saved_labels = set(l.strip() for l in row['labels'].split(','))
160
- else:
161
- saved_labels = set()
162
-
163
- # Create the boolean state list for the checkboxes
164
  states = [label in saved_labels for label in ALL_LABELS]
165
  annotations[img_idx] = states
166
  except Exception as e:
167
  print(f"Error loading annotations: {e}")
168
- # In case of error, just refresh the current view without changes
169
- return display_image(current_index)
170
-
171
- # After loading, refresh the view to show the annotations for the current image
172
  return display_image(current_index)
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  # --- Gradio UI Definition ---
176
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
177
- gr.Markdown("## Dermatology Annotation Tool")
178
- with gr.Row():
179
- image_upload = gr.File(label="1. Upload Images", file_count="multiple", file_types=["image"])
180
- csv_upload = gr.File(label="2. (Optional) Upload Annotations CSV", file_types=[".csv"], visible=False)
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  checkbox_components = []
183
-
184
  with gr.Row(): # Main row for the four columns of labels
185
- # Column 1
186
- with gr.Column(scale=1, min_width=0):
187
- for section_name in column1_sections:
188
- if section_name in SECTION_LABELS:
189
- with gr.Group():
190
- gr.Markdown(f"### {section_name}")
191
- for lbl in SECTION_LABELS[section_name]:
192
- cb = gr.Checkbox(label=lbl)
193
- checkbox_components.append(cb)
194
-
195
- # Column 2
196
- with gr.Column(scale=1, min_width=0):
197
- for section_name in column2_sections:
198
- if section_name in SECTION_LABELS:
199
- with gr.Group():
200
- gr.Markdown(f"### {section_name}")
201
- for lbl in SECTION_LABELS[section_name]:
202
- cb = gr.Checkbox(label=lbl)
203
- checkbox_components.append(cb)
204
-
205
- # Column 3
206
- with gr.Column(scale=1, min_width=0):
207
- for section_name in column3_sections:
208
- if section_name in SECTION_LABELS:
209
- with gr.Group():
210
- gr.Markdown(f"### {section_name}")
211
- for lbl in SECTION_LABELS[section_name]:
212
- cb = gr.Checkbox(label=lbl)
213
- checkbox_components.append(cb)
214
-
215
- # Column 4
216
- with gr.Column(scale=1, min_width=0):
217
- for section_name in column4_sections:
218
- if section_name in SECTION_LABELS:
219
- with gr.Group():
220
- gr.Markdown(f"### {section_name}")
221
- for lbl in SECTION_LABELS[section_name]:
222
- cb = gr.Checkbox(label=lbl)
223
- checkbox_components.append(cb)
224
-
225
- # Image display and controls
226
- with gr.Row():
227
- with gr.Column(scale=2): # Image display column
228
- img = gr.Image(label="Image")
229
- caption = gr.Label(value="No images uploaded")
230
- with gr.Row():
231
- prev_btn = gr.Button("⬅️ Previous")
232
- next_btn = gr.Button("Next ➡️")
233
- with gr.Column(scale=1): # Controls and download column
234
- submit_btn = gr.Button("Submit Labels")
235
- status = gr.Label()
236
- csv_downloader = gr.File(label="Download labels CSV")
237
 
238
  # --- Event Handling ---
239
 
240
- # When images are uploaded
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  image_upload.upload(
242
  fn=upload_images,
243
  inputs=image_upload,
244
  outputs=[img, caption] + checkbox_components + [image_upload, csv_upload]
245
  )
246
-
247
- # When a CSV annotation file is uploaded
248
  csv_upload.upload(
249
  fn=load_annotations,
250
  inputs=csv_upload,
251
  outputs=[img, caption] + checkbox_components
252
  )
253
-
254
- # When previous button is clicked
255
  prev_btn.click(
256
  fn=lambda: navigate(-1),
257
  outputs=[img, caption] + checkbox_components
258
  )
259
-
260
- # When next button is clicked
261
  next_btn.click(
262
  fn=lambda: navigate(1),
263
  outputs=[img, caption] + checkbox_components
264
  )
265
-
266
- # When submit button is clicked
267
  submit_btn.click(
268
  fn=submit,
269
  inputs=checkbox_components,
@@ -271,4 +258,4 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
271
  )
272
 
273
  if __name__ == "__main__":
274
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
2
  import pandas as pd
3
  from PIL import Image
4
  import os
5
+ from collections import Counter
6
+
7
+ # --- App Configuration ---
8
 
9
  # Define your sections and labels
10
  SECTION_LABELS = {
11
  "Oil Pore Related Issues": [
12
+ "Very Large Pores (Not Red)", "Whiteheads (Clogged Pores)", "Blackheads (Clogged Pores)",
13
+ "Shinny Skin", "Sebaceous Filaments (Sebum)"
 
 
 
14
  ],
15
  "Acne and Blemishes": [
16
+ "Pustules", "Papules", "Nodules", "Cysts", "Acne", "Rosacea",
17
+ "Telangiectasia", "Milia", "Scars", "Ice Berg Scars",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ],
19
+ "Redness and Irritation": ["Redness", "Irritation"],
20
+ "Dryness and Texture Issues": ["Dryness", "Fine Lines / Wrinkles", "Skin Flakes"],
21
+ "Aging and Elasticity Issues": ["Loose Skin", "Deep Wrinkles"],
22
+ "Pigmentation Issues": ["Dark Spots", "Melasma", "Freckles"],
23
  "Rosacea": [
24
+ "Erythematotelangiectatic Rosacea", "Papulopustular Rosacea",
25
+ "Phymatous Rosacea", "Ocular Rosacea"
 
 
26
  ],
27
+ "Eczema": ["Deborrheic Dermatitis"]
 
 
28
  }
29
 
30
  # Define the sections for each column to control UI layout
 
36
  # Combine all section lists to define the exact UI order
37
  UI_ORDERED_SECTIONS = column1_sections + column2_sections + column3_sections + column4_sections
38
 
39
+ # Flattened labels list, created in the SAME order as the UI checkboxes will be.
40
  ALL_LABELS = [
41
  label
42
  for section_name in UI_ORDERED_SECTIONS
43
  for label in SECTION_LABELS.get(section_name, [])
44
  ]
45
 
46
+ # --- Global State ---
 
47
  images = []
48
  current_index = 0
49
  results = []
50
  annotations = {}
51
 
52
+ # --- Core Annotation Functions ---
53
 
54
  def display_image(idx):
55
  """Displays the image at the given index and its saved annotations."""
 
63
  return [img, caption] + states
64
  return [None, "No images uploaded"] + [False] * len(ALL_LABELS)
65
 
 
66
  def navigate(delta):
67
  """Navigates to the next or previous image."""
68
  global current_index
 
71
  current_index = (current_index + delta + len(images)) % len(images)
72
  return display_image(current_index)
73
 
 
74
  def submit(*selections):
75
  """Saves the current annotations to the state and writes to a CSV file."""
76
  if not images:
77
  return "No image to label", None
 
 
78
  annotations[current_index] = list(selections)
79
  fname = os.path.basename(images[current_index])
80
  chosen_labels = [lbl for lbl, sel in zip(ALL_LABELS, selections) if sel]
81
 
82
  global results
 
83
  results = [r for r in results if r['image'] != fname]
84
  results.append({'image': fname, 'labels': ', '.join(chosen_labels)})
 
 
85
  df = pd.DataFrame(results)
86
  df.to_csv('image_labels.csv', index=False)
 
87
  return "Labels saved!", 'image_labels.csv'
88
 
 
89
  def upload_images(files):
90
+ """Handles image uploads, resetting the annotation state."""
91
  global images, current_index, results, annotations
92
  images = [f.name for f in files]
93
  current_index = 0
 
101
  """Loads annotations from an uploaded CSV file."""
102
  global annotations, results
103
  if csv_file is None or not images:
 
104
  return display_image(current_index)
 
105
  try:
106
  df = pd.read_csv(csv_file.name)
 
107
  image_map = {os.path.basename(name): i for i, name in enumerate(images)}
 
 
108
  annotations = {}
109
  results = df.to_dict('records')
 
110
  for _, row in df.iterrows():
111
  fname = row['image']
 
112
  if fname in image_map:
113
  img_idx = image_map[fname]
114
+ saved_labels = set(l.strip() for l in row['labels'].split(',')) if pd.notna(row['labels']) else set()
 
 
 
 
 
 
115
  states = [label in saved_labels for label in ALL_LABELS]
116
  annotations[img_idx] = states
117
  except Exception as e:
118
  print(f"Error loading annotations: {e}")
 
 
 
 
119
  return display_image(current_index)
120
 
121
+ # --- New Statistics and Mode-Switching Functions ---
122
+
123
+ def calculate_statistics(files):
124
+ """Reads multiple CSVs and calculates the frequency of each label."""
125
+ if not files:
126
+ return [gr.update() for _ in ALL_LABELS] # No change
127
+
128
+ label_counts = Counter()
129
+ for file_obj in files:
130
+ try:
131
+ df = pd.read_csv(file_obj.name)
132
+ if 'labels' in df.columns:
133
+ df.dropna(subset=['labels'], inplace=True)
134
+ for label_str in df['labels']:
135
+ labels = [l.strip() for l in label_str.split(',')]
136
+ label_counts.update(labels)
137
+ except Exception as e:
138
+ print(f"Could not process file {file_obj.name}: {e}")
139
+ continue
140
+
141
+ # Create the list of gr.update objects for the checkbox labels
142
+ updated_checkboxes = []
143
+ for label in ALL_LABELS:
144
+ count = label_counts.get(label, 0)
145
+ new_label_text = f"{label} (Count: {count})"
146
+ updated_checkboxes.append(gr.update(label=new_label_text))
147
+ return updated_checkboxes
148
+
149
+ def toggle_mode(current_mode):
150
+ """Switches the UI between Annotation and Statistics modes."""
151
+ if current_mode == "Annotation":
152
+ new_mode = "Statistics"
153
+ btn_text = "Switch to Annotation Mode"
154
+ anno_visible = False
155
+ stats_visible = True
156
+ # Return default labels when switching away from annotation mode
157
+ label_updates = [gr.update(label=lbl) for lbl in ALL_LABELS]
158
+ else: # Current mode is "Statistics"
159
+ new_mode = "Annotation"
160
+ btn_text = "Switch to Statistics Mode"
161
+ anno_visible = True
162
+ stats_visible = False
163
+ # Reset labels back to their original names
164
+ label_updates = [gr.update(label=lbl) for lbl in ALL_LABELS]
165
+
166
+ return [new_mode, gr.update(value=btn_text), gr.update(visible=anno_visible), gr.update(visible=stats_visible)] + label_updates
167
 
168
  # --- Gradio UI Definition ---
169
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
170
+ gr.Markdown("# Dermatology Annotation & Statistics Tool")
171
+
172
+ # State to track the current mode
173
+ app_mode = gr.State("Annotation")
174
 
175
+ with gr.Row():
176
+ mode_toggle_btn = gr.Button("Switch to Statistics Mode")
177
+
178
+ # --- Annotation Mode UI ---
179
+ with gr.Group(visible=True) as annotation_ui:
180
+ with gr.Row():
181
+ image_upload = gr.File(label="1. Upload Images", file_count="multiple", file_types=["image"])
182
+ csv_upload = gr.File(label="2. (Optional) Upload Annotations CSV", file_types=[".csv"], visible=False)
183
+ # Image display and controls
184
+ with gr.Row():
185
+ with gr.Column(scale=2):
186
+ img = gr.Image(label="Image")
187
+ caption = gr.Label(value="No images uploaded")
188
+ with gr.Row():
189
+ prev_btn = gr.Button("⬅️ Previous")
190
+ next_btn = gr.Button("Next ➡️")
191
+ with gr.Column(scale=1):
192
+ submit_btn = gr.Button("Submit Labels")
193
+ status = gr.Label()
194
+ csv_downloader = gr.File(label="Download labels CSV")
195
+
196
+ # --- Statistics Mode UI ---
197
+ with gr.Group(visible=False) as statistics_ui:
198
+ stats_csv_upload = gr.File(
199
+ label="Upload one or more annotation CSV files",
200
+ file_count="multiple",
201
+ file_types=[".csv"]
202
+ )
203
+
204
+ gr.Markdown("---")
205
+
206
+ # --- Shared UI (Checkboxes) ---
207
  checkbox_components = []
 
208
  with gr.Row(): # Main row for the four columns of labels
209
+ for col_sections in [column1_sections, column2_sections, column3_sections, column4_sections]:
210
+ with gr.Column(scale=1, min_width=220):
211
+ for section_name in col_sections:
212
+ if section_name in SECTION_LABELS:
213
+ with gr.Group():
214
+ gr.Markdown(f"### {section_name}")
215
+ for lbl in SECTION_LABELS[section_name]:
216
+ cb = gr.Checkbox(label=lbl)
217
+ checkbox_components.append(cb)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
  # --- Event Handling ---
220
 
221
+ # Mode switching
222
+ mode_toggle_btn.click(
223
+ fn=toggle_mode,
224
+ inputs=app_mode,
225
+ outputs=[app_mode, mode_toggle_btn, annotation_ui, statistics_ui] + checkbox_components
226
+ )
227
+
228
+ # Statistics calculation
229
+ stats_csv_upload.upload(
230
+ fn=calculate_statistics,
231
+ inputs=stats_csv_upload,
232
+ outputs=checkbox_components
233
+ )
234
+
235
+ # Annotation functionality
236
  image_upload.upload(
237
  fn=upload_images,
238
  inputs=image_upload,
239
  outputs=[img, caption] + checkbox_components + [image_upload, csv_upload]
240
  )
 
 
241
  csv_upload.upload(
242
  fn=load_annotations,
243
  inputs=csv_upload,
244
  outputs=[img, caption] + checkbox_components
245
  )
 
 
246
  prev_btn.click(
247
  fn=lambda: navigate(-1),
248
  outputs=[img, caption] + checkbox_components
249
  )
 
 
250
  next_btn.click(
251
  fn=lambda: navigate(1),
252
  outputs=[img, caption] + checkbox_components
253
  )
 
 
254
  submit_btn.click(
255
  fn=submit,
256
  inputs=checkbox_components,
 
258
  )
259
 
260
  if __name__ == "__main__":
261
+ demo.launch(server_name="0.0.0.0", server_port=7860)