nmariotto commited on
Commit
2acbec7
·
verified ·
1 Parent(s): f7045ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -57
app.py CHANGED
@@ -16,6 +16,9 @@ from googleapiclient.http import MediaIoBaseUpload
16
  import gspread
17
  import time
18
 
 
 
 
19
  # 🔥 Inicializar Roboflow
20
  API_KEY = st.secrets["roboflow_api_key"]
21
  rf = roboflow.Roboflow(api_key=API_KEY)
@@ -24,12 +27,32 @@ model = project.version(st.secrets["roboflow_version"]).model
24
  model.confidence = 80
25
  model.overlap = 25
26
  dpi_value = 300
27
- APP_VERSION = "2.4"
28
-
29
 
30
  with st.expander("⚙️ Advanced Settings", expanded=True):
31
  model.confidence = st.slider("Model Confidence (%)", 20, 100, 80)
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # 📁 Setup Google Drive e Sheets com OAuth 2.0
34
  scope = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets"]
35
  credentials = Credentials(
@@ -49,6 +72,7 @@ def calculate_polygon_area(points):
49
  polygon = Polygon([(p['x'], p['y']) for p in points])
50
  return polygon.area
51
 
 
52
  def safe_predict(image_path):
53
  for attempt in range(3):
54
  try:
@@ -57,9 +81,11 @@ def safe_predict(image_path):
57
  time.sleep(1)
58
  return None
59
 
 
60
  def resize_image(image):
61
  return image.resize((640, 640))
62
 
 
63
  def upload_to_drive(image_bytes, filename, folder_id):
64
  media = MediaIoBaseUpload(image_bytes, mimetype='image/png')
65
  drive_service.files().create(
@@ -68,6 +94,7 @@ def upload_to_drive(image_bytes, filename, folder_id):
68
  fields='id'
69
  ).execute()
70
 
 
71
  def find_or_create_folder(folder_name, parent=None):
72
  query = f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
73
  if parent:
@@ -85,39 +112,61 @@ def find_or_create_folder(folder_name, parent=None):
85
  file = drive_service.files().create(body=file_metadata, fields='id').execute()
86
  return file.get('id')
87
 
 
88
  def get_image_bytes(image):
89
  buf = BytesIO()
90
  image.save(buf, format="PNG")
91
  buf.seek(0)
92
  return buf
93
 
94
- def process_image(uploaded_file):
 
95
  try:
96
  safe_name = uploaded_file.name.replace(" ", "_")
97
  image = Image.open(uploaded_file).convert("RGB")
98
 
 
 
 
 
 
 
 
 
 
 
 
99
  with tempfile.NamedTemporaryFile(suffix=".png", delete=True) as temp_file:
100
  image.save(temp_file.name)
101
  prediction = safe_predict(temp_file.name)
102
  if not prediction:
103
  return {
104
  "Imagem": safe_name,
 
 
105
  "SemSegmentacao": True,
106
  "Exibir": image,
107
- "Original": get_image_bytes(image)
108
  }
109
  prediction_data = prediction.json()
110
 
111
  if not prediction_data["predictions"]:
112
  return {
113
  "Imagem": safe_name,
 
 
114
  "SemSegmentacao": True,
115
  "Exibir": image,
116
- "Original": get_image_bytes(image)
117
  }
118
 
119
  points = prediction_data["predictions"][0]["points"]
120
- area = calculate_polygon_area(points)
 
 
 
 
 
121
  x = [p['x'] for p in points] + [points[0]['x']]
122
  y = [p['y'] for p in points] + [points[0]['y']]
123
 
@@ -141,51 +190,117 @@ def process_image(uploaded_file):
141
 
142
  return {
143
  "Imagem": safe_name,
144
- "Área Segmentada (px²)": area,
 
145
  "Original": original_buffer,
146
  "Segmentada": segmented_buffer,
147
  "Poligono": polygon_buffer,
148
  "Exibir": image,
149
- "SemSegmentacao": False
150
  }
151
 
152
  except:
153
  return None
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  # 🗂️ Interface principal
156
  st.title("IA Model Segmentation")
157
- st.caption(f"Version {APP_VERSION}")
158
  upload_option = st.radio("Choose upload type:", ["Single image", "Image folder"])
159
-
160
-
161
  results = []
162
 
163
  if upload_option == "Single image":
164
  uploaded_file = st.file_uploader("Choose an image", type=["png", "jpg", "jpeg", "tiff"])
165
  if uploaded_file:
166
- result = process_image(uploaded_file)
167
  if result:
168
  results.append(result)
169
  st.image(result["Exibir"], caption=f"Original Image - {result['Imagem']}", use_container_width=True)
 
170
  if not result["SemSegmentacao"]:
171
  st.image(result["Segmentada"], caption="Segmentation", use_container_width=True)
172
  st.image(result["Poligono"], caption="Polygon", use_container_width=True)
173
- st.write(f"📏 **Segmented Area:** {result['Área Segmentada (px²)']:.2f} pixels²")
 
 
 
 
 
 
 
174
 
175
  st.download_button(
176
  label="📥 Download Segmented Image",
177
  data=result["Segmentada"],
178
  file_name="segmented_images.png",
179
- mime="image/png"
180
  )
181
  else:
182
  st.warning("⚠️ No segmentation was detected in this image.")
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  elif upload_option == "Image folder":
185
- uploaded_files = st.file_uploader("Upload multiple images", type=["png", "jpg", "jpeg", "tiff"], accept_multiple_files=True)
 
 
 
 
186
  if uploaded_files:
 
 
 
187
  with ThreadPoolExecutor(max_workers=4) as executor:
188
- processed = list(executor.map(process_image, uploaded_files))
189
 
190
  falhas = [f.name for f, r in zip(uploaded_files, processed) if r and r.get("SemSegmentacao")]
191
  if falhas:
@@ -197,18 +312,56 @@ elif upload_option == "Image folder":
197
  if result:
198
  results.append(result)
199
  st.image(result["Exibir"], caption=f"Original Image - {result['Imagem']}", use_container_width=True)
 
200
  if not result["SemSegmentacao"]:
201
  st.image(result["Segmentada"], caption="Segmentation", use_container_width=True)
202
  st.image(result["Poligono"], caption="Polygon", use_container_width=True)
203
- st.write(f"📏 **Segmented Area:** {result['Área Segmentada (px²)']:.2f} pixels²")
 
 
 
 
 
 
 
 
204
  zip_file.writestr(f"segmentada_{result['Imagem']}.png", result["Segmentada"].getvalue())
205
  zip_file.writestr(f"poligono_{result['Imagem']}.png", result["Poligono"].getvalue())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  zip_images_buffer.seek(0)
208
 
209
  if results:
210
  df = pd.DataFrame([
211
- { "Image": r["Imagem"], "Segmented Area (px²)": r["Área Segmentada (px²)"] if not r["SemSegmentacao"] else "No Segmentation" }
 
 
 
 
 
 
 
 
 
 
 
 
212
  for r in results
213
  ])
214
  st.markdown("### 📊 Results Table")
@@ -218,42 +371,15 @@ elif upload_option == "Image folder":
218
  df.to_excel(excel_buffer, index=False)
219
  excel_buffer.seek(0)
220
 
221
- st.download_button("📥 Download Table (Excel)", data=excel_buffer, file_name="segmentation_results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
222
- st.download_button("📥 Download Segmented Images", data=zip_images_buffer, file_name="segmented_images.zip", mime="application/zip")
223
-
224
- # 📝 Manual Feedback
225
- if results:
226
- st.markdown("## 📝 Feedback")
227
- imagem_escolhida = st.selectbox("Select an image to evaluate:", [r["Imagem"] for r in results])
228
- avaliacao = st.radio("How do you evaluate this segmentation?", ["Great", "Acceptable", "Bad", "No segmentation"], horizontal=True)
229
- observacao = st.text_area("Observations (optional):")
230
-
231
- if st.button("Save Feedback"):
232
- row = [imagem_escolhida, avaliacao, observacao]
233
- sheet.append_row(row)
234
-
235
- if avaliacao in ["Acceptable", "Bad", "No segmentation"]:
236
- sufixo = "aceitavel" if avaliacao == "Acceptable" else "ruim" if avaliacao == "Bad" else "sem_segmentacao"
237
- parent_folder = find_or_create_folder("Feedback Segmentacoes")
238
- subfolder = find_or_create_folder(imagem_escolhida.replace(".png", ""), parent_folder)
239
-
240
- for r in results:
241
- if r["Imagem"] == imagem_escolhida:
242
- resized_original = resize_image(r["Exibir"])
243
- buffer = BytesIO()
244
- resized_original.save(buffer, format="PNG")
245
- buffer.seek(0)
246
- upload_to_drive(buffer, f"original_{sufixo}.png", subfolder)
247
-
248
- if avaliacao != "No segmentation" and "Segmentada" in r and "Poligono" in r:
249
- resized_segmented = resize_image(Image.open(BytesIO(r["Segmentada"].getvalue())))
250
- resized_polygon = resize_image(Image.open(BytesIO(r["Poligono"].getvalue())))
251
-
252
- for img_obj, nome in zip([resized_segmented, resized_polygon], ["segmentada", "poligono"]):
253
- buffer = BytesIO()
254
- img_obj.save(buffer, format="PNG")
255
- buffer.seek(0)
256
- upload_to_drive(buffer, f"{nome}_{sufixo}.png", subfolder)
257
- break
258
-
259
- st.success("✅ Feedback saved successfully!")
 
16
  import gspread
17
  import time
18
 
19
+ APP_VERSION = "2.4"
20
+
21
+
22
  # 🔥 Inicializar Roboflow
23
  API_KEY = st.secrets["roboflow_api_key"]
24
  rf = roboflow.Roboflow(api_key=API_KEY)
 
27
  model.confidence = 80
28
  model.overlap = 25
29
  dpi_value = 300
 
 
30
 
31
  with st.expander("⚙️ Advanced Settings", expanded=True):
32
  model.confidence = st.slider("Model Confidence (%)", 20, 100, 80)
33
 
34
+ st.markdown(
35
+ "### Physical calibration (optional)\n"
36
+ "Provide the physical scale to convert pixel area to µm². "
37
+ "If left empty, results will be reported only in pixels²."
38
+ )
39
+
40
+ col1, col2 = st.columns(2)
41
+ fov_um = col1.number_input(
42
+ "Field of view width (µm)",
43
+ min_value=0.0,
44
+ value=0.0,
45
+ step=1.0,
46
+ help="Physical width of the image field, in micrometers."
47
+ )
48
+ pixel_size_um = col2.number_input(
49
+ "Pixel size (µm / pixel)",
50
+ min_value=0.0,
51
+ value=0.0,
52
+ step=0.01,
53
+ help="If provided, this value overrides the FOV-based calibration."
54
+ )
55
+
56
  # 📁 Setup Google Drive e Sheets com OAuth 2.0
57
  scope = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets"]
58
  credentials = Credentials(
 
72
  polygon = Polygon([(p['x'], p['y']) for p in points])
73
  return polygon.area
74
 
75
+
76
  def safe_predict(image_path):
77
  for attempt in range(3):
78
  try:
 
81
  time.sleep(1)
82
  return None
83
 
84
+
85
  def resize_image(image):
86
  return image.resize((640, 640))
87
 
88
+
89
  def upload_to_drive(image_bytes, filename, folder_id):
90
  media = MediaIoBaseUpload(image_bytes, mimetype='image/png')
91
  drive_service.files().create(
 
94
  fields='id'
95
  ).execute()
96
 
97
+
98
  def find_or_create_folder(folder_name, parent=None):
99
  query = f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
100
  if parent:
 
112
  file = drive_service.files().create(body=file_metadata, fields='id').execute()
113
  return file.get('id')
114
 
115
+
116
  def get_image_bytes(image):
117
  buf = BytesIO()
118
  image.save(buf, format="PNG")
119
  buf.seek(0)
120
  return buf
121
 
122
+
123
+ def process_image(uploaded_file, fov_um=None, pixel_size_um=None):
124
  try:
125
  safe_name = uploaded_file.name.replace(" ", "_")
126
  image = Image.open(uploaded_file).convert("RGB")
127
 
128
+ # Image dimensions for physical calibration
129
+ width_px, height_px = image.size
130
+
131
+ # Determine effective pixel size in µm/pixel
132
+ effective_pixel_size_um = None
133
+ if pixel_size_um is not None and pixel_size_um > 0:
134
+ effective_pixel_size_um = pixel_size_um
135
+ elif fov_um is not None and fov_um > 0:
136
+ # Assume FOV refers to the horizontal field of view
137
+ effective_pixel_size_um = fov_um / float(width_px)
138
+
139
  with tempfile.NamedTemporaryFile(suffix=".png", delete=True) as temp_file:
140
  image.save(temp_file.name)
141
  prediction = safe_predict(temp_file.name)
142
  if not prediction:
143
  return {
144
  "Imagem": safe_name,
145
+ "Área Segmentada (px²)": None,
146
+ "Área Segmentada (µm²)": None,
147
  "SemSegmentacao": True,
148
  "Exibir": image,
149
+ "Original": get_image_bytes(image),
150
  }
151
  prediction_data = prediction.json()
152
 
153
  if not prediction_data["predictions"]:
154
  return {
155
  "Imagem": safe_name,
156
+ "Área Segmentada (px²)": None,
157
+ "Área Segmentada (µm²)": None,
158
  "SemSegmentacao": True,
159
  "Exibir": image,
160
+ "Original": get_image_bytes(image),
161
  }
162
 
163
  points = prediction_data["predictions"][0]["points"]
164
+ area_px2 = calculate_polygon_area(points)
165
+
166
+ area_um2 = None
167
+ if effective_pixel_size_um is not None:
168
+ area_um2 = area_px2 * (effective_pixel_size_um ** 2)
169
+
170
  x = [p['x'] for p in points] + [points[0]['x']]
171
  y = [p['y'] for p in points] + [points[0]['y']]
172
 
 
190
 
191
  return {
192
  "Imagem": safe_name,
193
+ "Área Segmentada (px²)": area_px2,
194
+ "Área Segmentada (µm²)": area_um2,
195
  "Original": original_buffer,
196
  "Segmentada": segmented_buffer,
197
  "Poligono": polygon_buffer,
198
  "Exibir": image,
199
+ "SemSegmentacao": False,
200
  }
201
 
202
  except:
203
  return None
204
 
205
+
206
+ def save_feedback(result, avaliacao, observacao):
207
+ image_name = result["Imagem"]
208
+
209
+ # Save feedback row to Google Sheet
210
+ row = [image_name, avaliacao, observacao]
211
+ sheet.append_row(row)
212
+
213
+ # Upload feedback images to Google Drive for curation
214
+ if avaliacao in ["Acceptable", "Bad", "No segmentation"]:
215
+ sufixo = (
216
+ "aceitavel" if avaliacao == "Acceptable"
217
+ else "ruim" if avaliacao == "Bad"
218
+ else "sem_segmentacao"
219
+ )
220
+ parent_folder = find_or_create_folder("Feedback Segmentacoes")
221
+ subfolder = find_or_create_folder(image_name.replace(".png", ""), parent_folder)
222
+
223
+ # Original image (always saved)
224
+ resized_original = resize_image(result["Exibir"])
225
+ buffer = BytesIO()
226
+ resized_original.save(buffer, format="PNG")
227
+ buffer.seek(0)
228
+ upload_to_drive(buffer, f"original_{sufixo}.png", subfolder)
229
+
230
+ # Segmented and polygon images (only if segmentation exists)
231
+ if avaliacao != "No segmentation" and "Segmentada" in result and "Poligono" in result:
232
+ resized_segmented = resize_image(Image.open(BytesIO(result["Segmentada"].getvalue())))
233
+ resized_polygon = resize_image(Image.open(BytesIO(result["Poligono"].getvalue())))
234
+
235
+ for img_obj, nome in zip([resized_segmented, resized_polygon], ["segmentada", "poligono"]):
236
+ buffer = BytesIO()
237
+ img_obj.save(buffer, format="PNG")
238
+ buffer.seek(0)
239
+ upload_to_drive(buffer, f"{nome}_{sufixo}.png", subfolder)
240
+
241
+
242
  # 🗂️ Interface principal
243
  st.title("IA Model Segmentation")
244
+ st.caption(f"Version {APP_VERSION} (model retrained with user feedback)")
245
  upload_option = st.radio("Choose upload type:", ["Single image", "Image folder"])
 
 
246
  results = []
247
 
248
  if upload_option == "Single image":
249
  uploaded_file = st.file_uploader("Choose an image", type=["png", "jpg", "jpeg", "tiff"])
250
  if uploaded_file:
251
+ result = process_image(uploaded_file, fov_um=fov_um, pixel_size_um=pixel_size_um)
252
  if result:
253
  results.append(result)
254
  st.image(result["Exibir"], caption=f"Original Image - {result['Imagem']}", use_container_width=True)
255
+
256
  if not result["SemSegmentacao"]:
257
  st.image(result["Segmentada"], caption="Segmentation", use_container_width=True)
258
  st.image(result["Poligono"], caption="Polygon", use_container_width=True)
259
+
260
+ area_px2 = result["Área Segmentada (px²)"]
261
+ area_um2 = result["Área Segmentada (µm²)"]
262
+
263
+ if area_px2 is not None:
264
+ st.write(f"📏 **Segmented Area:** {area_px2:.2f} pixels²")
265
+ if area_um2 is not None:
266
+ st.write(f"📏 **Segmented Area (calibrated):** {area_um2:.2f} µm²")
267
 
268
  st.download_button(
269
  label="📥 Download Segmented Image",
270
  data=result["Segmentada"],
271
  file_name="segmented_images.png",
272
+ mime="image/png",
273
  )
274
  else:
275
  st.warning("⚠️ No segmentation was detected in this image.")
276
 
277
+ st.markdown("## 📝 Feedback for this image")
278
+ avaliacao = st.radio(
279
+ "How do you evaluate this segmentation?",
280
+ ["Great", "Acceptable", "Bad", "No segmentation"],
281
+ horizontal=True,
282
+ key=f"single_radio_{result['Imagem']}",
283
+ )
284
+ observacao = st.text_area(
285
+ "Observations (optional):",
286
+ key=f"single_obs_{result['Imagem']}",
287
+ )
288
+ if st.button("Save Feedback", key=f"single_btn_{result['Imagem']}"):
289
+ save_feedback(result, avaliacao, observacao)
290
+ st.success("✅ Feedback saved successfully!")
291
+
292
  elif upload_option == "Image folder":
293
+ uploaded_files = st.file_uploader(
294
+ "Upload multiple images",
295
+ type=["png", "jpg", "jpeg", "tiff"],
296
+ accept_multiple_files=True,
297
+ )
298
  if uploaded_files:
299
+ def process_wrapper(f):
300
+ return process_image(f, fov_um=fov_um, pixel_size_um=pixel_size_um)
301
+
302
  with ThreadPoolExecutor(max_workers=4) as executor:
303
+ processed = list(executor.map(process_wrapper, uploaded_files))
304
 
305
  falhas = [f.name for f, r in zip(uploaded_files, processed) if r and r.get("SemSegmentacao")]
306
  if falhas:
 
312
  if result:
313
  results.append(result)
314
  st.image(result["Exibir"], caption=f"Original Image - {result['Imagem']}", use_container_width=True)
315
+
316
  if not result["SemSegmentacao"]:
317
  st.image(result["Segmentada"], caption="Segmentation", use_container_width=True)
318
  st.image(result["Poligono"], caption="Polygon", use_container_width=True)
319
+
320
+ area_px2 = result["Área Segmentada (px²)"]
321
+ area_um2 = result["Área Segmentada (µm²)"]
322
+
323
+ if area_px2 is not None:
324
+ st.write(f"📏 **Segmented Area:** {area_px2:.2f} pixels²")
325
+ if area_um2 is not None:
326
+ st.write(f"📏 **Segmented Area (calibrated):** {area_um2:.2f} µm²")
327
+
328
  zip_file.writestr(f"segmentada_{result['Imagem']}.png", result["Segmentada"].getvalue())
329
  zip_file.writestr(f"poligono_{result['Imagem']}.png", result["Poligono"].getvalue())
330
+ else:
331
+ st.warning("⚠️ No segmentation was detected in this image.")
332
+
333
+ st.markdown(f"#### 📝 Feedback – {result['Imagem']}")
334
+ avaliacao = st.radio(
335
+ "How do you evaluate this segmentation?",
336
+ ["Great", "Acceptable", "Bad", "No segmentation"],
337
+ horizontal=True,
338
+ key=f"folder_radio_{result['Imagem']}",
339
+ )
340
+ observacao = st.text_area(
341
+ "Observations (optional):",
342
+ key=f"folder_obs_{result['Imagem']}",
343
+ )
344
+ if st.button("Save Feedback", key=f"folder_btn_{result['Imagem']}"):
345
+ save_feedback(result, avaliacao, observacao)
346
+ st.success(f"✅ Feedback for {result['Imagem']} saved successfully.")
347
 
348
  zip_images_buffer.seek(0)
349
 
350
  if results:
351
  df = pd.DataFrame([
352
+ {
353
+ "Image": r["Imagem"],
354
+ "Segmented Area (px²)": (
355
+ r["Área Segmentada (px²)"]
356
+ if (not r["SemSegmentacao"] and r["Área Segmentada (px²)"] is not None)
357
+ else "No Segmentation"
358
+ ),
359
+ "Segmented Area (µm²)": (
360
+ f"{r['Área Segmentada (µm²)']:.2f}"
361
+ if (not r["SemSegmentacao"] and r["Área Segmentada (µm²)"] is not None)
362
+ else ""
363
+ ),
364
+ }
365
  for r in results
366
  ])
367
  st.markdown("### 📊 Results Table")
 
371
  df.to_excel(excel_buffer, index=False)
372
  excel_buffer.seek(0)
373
 
374
+ st.download_button(
375
+ "📥 Download Table (Excel)",
376
+ data=excel_buffer,
377
+ file_name="segmentation_results.xlsx",
378
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
379
+ )
380
+ st.download_button(
381
+ "📥 Download Segmented Images",
382
+ data=zip_images_buffer,
383
+ file_name="segmented_images.zip",
384
+ mime="application/zip",
385
+ )