SmartHeal commited on
Commit
691ce33
·
verified ·
1 Parent(s): 50425b5

Update src/ai_processor.py

Browse files
Files changed (1) hide show
  1. src/ai_processor.py +134 -134
src/ai_processor.py CHANGED
@@ -1196,142 +1196,142 @@ Automated analysis provides quantitative measurements; verify via clinical exami
1196
  seg_adjust: float = 0.0,
1197
  manual_mask_path: Optional[str] = None,
1198
  ) -> Dict:
1199
- """
1200
- Analyze a wound image and return a dictionary with visual analysis, report and paths.
1201
- """
1202
- try:
1203
- # Normalize input to PIL
1204
- if isinstance(image, str):
1205
- if not os.path.exists(image):
1206
- raise ValueError(f"Image file not found: {image}")
1207
- image_pil = Image.open(image)
1208
- elif isinstance(image, Image.Image):
1209
- image_pil = image
1210
- elif isinstance(image, np.ndarray):
1211
- image_pil = Image.fromarray(image)
1212
- else:
1213
- raise ValueError(f"Unsupported image type: {type(image)}")
1214
-
1215
- # Run the standard pipeline
1216
- result = self.full_analysis_pipeline(image_pil, questionnaire_data or {})
1217
-
1218
- # If neither manual mask nor adjustment specified, return as is
1219
- if (not manual_mask_path) and (abs(seg_adjust) < 1e-5):
1220
- return result
1221
-
1222
- # Extract visual analysis and calibration from result
1223
- visual = result.get("visual_analysis", {}) or {}
1224
- px_per_cm = float(visual.get("px_per_cm", DEFAULT_PX_PER_CM))
1225
-
1226
- # Attempt to load the ROI mask generated by the pipeline
1227
- roi_mask_path = visual.get("roi_mask_path")
1228
- mask_img = None
1229
-
1230
- # Use manual mask if provided
1231
- if manual_mask_path:
1232
- try:
1233
- if os.path.exists(manual_mask_path):
1234
- mask_img = Image.open(manual_mask_path)
1235
- else:
1236
- logging.warning(f"Manual mask path does not exist: {manual_mask_path}")
1237
- except Exception as e:
1238
- logging.warning(f"Failed to load manual mask: {e}")
1239
- elif roi_mask_path and os.path.exists(roi_mask_path):
1240
- # Otherwise load the automatically generated ROI mask
1241
- try:
1242
- mask_img = Image.open(roi_mask_path)
1243
- except Exception as e:
1244
- logging.warning(f"Failed to load ROI mask for adjustment: {e}")
1245
-
1246
- if mask_img is not None:
1247
- # Convert to numpy for processing
1248
- mask_np = np.array(mask_img.convert("L"))
1249
-
1250
- # If adjustment is requested and no manual mask override
1251
- if (manual_mask_path is None) and (abs(seg_adjust) >= 1e-5):
1252
- # Determine the number of iterations based on percentage; roughly 5% increments
1253
- iter_count = max(1, int(round(abs(seg_adjust) / 5)))
1254
- kernel = np.ones((3, 3), np.uint8)
1255
  try:
1256
- if seg_adjust > 0:
1257
- # Dilate (expand) mask
1258
- mask_np = cv2.dilate((mask_np > 127).astype(np.uint8), kernel, iterations=iter_count)
1259
  else:
1260
- # Erode (shrink) mask
1261
- mask_np = cv2.erode((mask_np > 127).astype(np.uint8), kernel, iterations=iter_count)
1262
  except Exception as e:
1263
- logging.warning(f"Segmentation adjustment failed: {e}")
1264
- else:
1265
- # If manual mask provided, binarize it directly
1266
- mask_np = (mask_np > 127).astype(np.uint8)
1267
-
1268
- # Recalculate length, width and area using the adjusted or manual mask
1269
- try:
1270
- length_cm, breadth_cm, area_cm2 = self._refine_metrics_from_mask(mask_np, px_per_cm)
1271
- visual["length_cm"] = length_cm
1272
- visual["breadth_cm"] = breadth_cm
1273
- visual["surface_area_cm2"] = area_cm2
1274
- # Indicate that segmentation was refined manually or adjusted
1275
- visual["segmentation_refined"] = bool(manual_mask_path) or (abs(seg_adjust) >= 1e-5)
1276
- except Exception as e:
1277
- logging.warning(f"Failed to recalculate metrics from mask: {e}")
1278
-
1279
- # --- NEW: if a manual mask was supplied, create & store a manual overlay and update paths ---
1280
- if manual_mask_path:
1281
  try:
1282
- # Base image for overlay
1283
- base_rgb = np.array(image_pil.convert("RGB"))
1284
- base_bgr = cv2.cvtColor(base_rgb, cv2.COLOR_RGB2BGR)
1285
- h, w = base_bgr.shape[:2]
1286
-
1287
- # Ensure mask matches base size
1288
- if mask_np.shape[:2] != (h, w):
1289
- mask_np = cv2.resize(mask_np.astype(np.uint8), (w, h), interpolation=cv2.INTER_NEAREST)
1290
-
1291
- # Decide output directory
1292
- out_dir = os.path.dirname(roi_mask_path or result.get("saved_image_path") or manual_mask_path)
1293
- if not out_dir or not os.path.exists(out_dir):
1294
- out_dir = os.getcwd()
1295
-
1296
- ts = datetime.now().strftime("%Y%m%d_%H%M%S")
1297
-
1298
- # Save a clean binary manual mask (0/255)
1299
- manual_mask_save = os.path.join(out_dir, f"manual_mask_{ts}.png")
1300
- cv2.imwrite(manual_mask_save, (mask_np * 255).astype(np.uint8))
1301
-
1302
- # Build red overlay with white contour
1303
- red = np.zeros_like(base_bgr); red[:] = (0, 0, 255)
1304
- alpha = 0.55
1305
- tinted = cv2.addWeighted(base_bgr, 1 - alpha, red, alpha, 0)
1306
- mask3 = cv2.merge([(mask_np * 255).astype(np.uint8)] * 3)
1307
- overlay = np.where(mask3 > 0, tinted, base_bgr)
1308
-
1309
- cnts, _ = cv2.findContours((mask_np * 255).astype(np.uint8),
1310
- cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1311
- if cnts:
1312
- cv2.drawContours(overlay, cnts, -1, (255, 255, 255), 2)
1313
-
1314
- manual_overlay_path = os.path.join(out_dir, f"segmentation_manual_{ts}.png")
1315
- cv2.imwrite(manual_overlay_path, overlay)
1316
-
1317
- # Update paths so the UI shows the MANUAL overlay/mask
1318
- visual["roi_mask_path"] = manual_mask_save
1319
- visual["segmentation_image_path"] = manual_overlay_path
1320
- visual["segmentation_roi_path"] = manual_overlay_path # alias some UIs read
1321
- visual["segmentation_refined_type"] = "manual"
1322
- visual["manual_mask_used"] = True
1323
  except Exception as e:
1324
- logging.warning(f"Failed to generate manual segmentation overlay: {e}")
1325
-
1326
- result["visual_analysis"] = visual
1327
- return result
1328
- except Exception as e:
1329
- logging.error(f"Wound analysis error: {e}")
1330
- return {
1331
- "success": False,
1332
- "error": str(e),
1333
- "visual_analysis": {},
1334
- "report": f"Analysis initialization failed: {str(e)}",
1335
- "saved_image_path": None,
1336
- "guideline_context": "",
1337
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1196
  seg_adjust: float = 0.0,
1197
  manual_mask_path: Optional[str] = None,
1198
  ) -> Dict:
1199
+ """
1200
+ Analyze a wound image and return a dictionary with visual analysis, report and paths.
1201
+ """
1202
+ try:
1203
+ # Normalize input to PIL
1204
+ if isinstance(image, str):
1205
+ if not os.path.exists(image):
1206
+ raise ValueError(f"Image file not found: {image}")
1207
+ image_pil = Image.open(image)
1208
+ elif isinstance(image, Image.Image):
1209
+ image_pil = image
1210
+ elif isinstance(image, np.ndarray):
1211
+ image_pil = Image.fromarray(image)
1212
+ else:
1213
+ raise ValueError(f"Unsupported image type: {type(image)}")
1214
+
1215
+ # Run the standard pipeline
1216
+ result = self.full_analysis_pipeline(image_pil, questionnaire_data or {})
1217
+
1218
+ # If neither manual mask nor adjustment specified, return as is
1219
+ if (not manual_mask_path) and (abs(seg_adjust) < 1e-5):
1220
+ return result
1221
+
1222
+ # Extract visual analysis and calibration from result
1223
+ visual = result.get("visual_analysis", {}) or {}
1224
+ px_per_cm = float(visual.get("px_per_cm", DEFAULT_PX_PER_CM))
1225
+
1226
+ # Attempt to load the ROI mask generated by the pipeline
1227
+ roi_mask_path = visual.get("roi_mask_path")
1228
+ mask_img = None
1229
+
1230
+ # Use manual mask if provided
1231
+ if manual_mask_path:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1232
  try:
1233
+ if os.path.exists(manual_mask_path):
1234
+ mask_img = Image.open(manual_mask_path)
 
1235
  else:
1236
+ logging.warning(f"Manual mask path does not exist: {manual_mask_path}")
 
1237
  except Exception as e:
1238
+ logging.warning(f"Failed to load manual mask: {e}")
1239
+ elif roi_mask_path and os.path.exists(roi_mask_path):
1240
+ # Otherwise load the automatically generated ROI mask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1241
  try:
1242
+ mask_img = Image.open(roi_mask_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  except Exception as e:
1244
+ logging.warning(f"Failed to load ROI mask for adjustment: {e}")
1245
+
1246
+ if mask_img is not None:
1247
+ # Convert to numpy for processing
1248
+ mask_np = np.array(mask_img.convert("L"))
1249
+
1250
+ # If adjustment is requested and no manual mask override
1251
+ if (manual_mask_path is None) and (abs(seg_adjust) >= 1e-5):
1252
+ # Determine the number of iterations based on percentage; roughly 5% increments
1253
+ iter_count = max(1, int(round(abs(seg_adjust) / 5)))
1254
+ kernel = np.ones((3, 3), np.uint8)
1255
+ try:
1256
+ if seg_adjust > 0:
1257
+ # Dilate (expand) mask
1258
+ mask_np = cv2.dilate((mask_np > 127).astype(np.uint8), kernel, iterations=iter_count)
1259
+ else:
1260
+ # Erode (shrink) mask
1261
+ mask_np = cv2.erode((mask_np > 127).astype(np.uint8), kernel, iterations=iter_count)
1262
+ except Exception as e:
1263
+ logging.warning(f"Segmentation adjustment failed: {e}")
1264
+ else:
1265
+ # If manual mask provided, binarize it directly
1266
+ mask_np = (mask_np > 127).astype(np.uint8)
1267
+
1268
+ # Recalculate length, width and area using the adjusted or manual mask
1269
+ try:
1270
+ length_cm, breadth_cm, area_cm2 = self._refine_metrics_from_mask(mask_np, px_per_cm)
1271
+ visual["length_cm"] = length_cm
1272
+ visual["breadth_cm"] = breadth_cm
1273
+ visual["surface_area_cm2"] = area_cm2
1274
+ # Indicate that segmentation was refined manually or adjusted
1275
+ visual["segmentation_refined"] = bool(manual_mask_path) or (abs(seg_adjust) >= 1e-5)
1276
+ except Exception as e:
1277
+ logging.warning(f"Failed to recalculate metrics from mask: {e}")
1278
+
1279
+ # --- NEW: if a manual mask was supplied, create & store a manual overlay and update paths ---
1280
+ if manual_mask_path:
1281
+ try:
1282
+ # Base image for overlay
1283
+ base_rgb = np.array(image_pil.convert("RGB"))
1284
+ base_bgr = cv2.cvtColor(base_rgb, cv2.COLOR_RGB2BGR)
1285
+ h, w = base_bgr.shape[:2]
1286
+
1287
+ # Ensure mask matches base size
1288
+ if mask_np.shape[:2] != (h, w):
1289
+ mask_np = cv2.resize(mask_np.astype(np.uint8), (w, h), interpolation=cv2.INTER_NEAREST)
1290
+
1291
+ # Decide output directory
1292
+ out_dir = os.path.dirname(roi_mask_path or result.get("saved_image_path") or manual_mask_path)
1293
+ if not out_dir or not os.path.exists(out_dir):
1294
+ out_dir = os.getcwd()
1295
+
1296
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
1297
+
1298
+ # Save a clean binary manual mask (0/255)
1299
+ manual_mask_save = os.path.join(out_dir, f"manual_mask_{ts}.png")
1300
+ cv2.imwrite(manual_mask_save, (mask_np * 255).astype(np.uint8))
1301
+
1302
+ # Build red overlay with white contour
1303
+ red = np.zeros_like(base_bgr); red[:] = (0, 0, 255)
1304
+ alpha = 0.55
1305
+ tinted = cv2.addWeighted(base_bgr, 1 - alpha, red, alpha, 0)
1306
+ mask3 = cv2.merge([(mask_np * 255).astype(np.uint8)] * 3)
1307
+ overlay = np.where(mask3 > 0, tinted, base_bgr)
1308
+
1309
+ cnts, _ = cv2.findContours((mask_np * 255).astype(np.uint8),
1310
+ cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1311
+ if cnts:
1312
+ cv2.drawContours(overlay, cnts, -1, (255, 255, 255), 2)
1313
+
1314
+ manual_overlay_path = os.path.join(out_dir, f"segmentation_manual_{ts}.png")
1315
+ cv2.imwrite(manual_overlay_path, overlay)
1316
+
1317
+ # Update paths so the UI shows the MANUAL overlay/mask
1318
+ visual["roi_mask_path"] = manual_mask_save
1319
+ visual["segmentation_image_path"] = manual_overlay_path
1320
+ visual["segmentation_roi_path"] = manual_overlay_path # alias some UIs read
1321
+ visual["segmentation_refined_type"] = "manual"
1322
+ visual["manual_mask_used"] = True
1323
+ except Exception as e:
1324
+ logging.warning(f"Failed to generate manual segmentation overlay: {e}")
1325
+
1326
+ result["visual_analysis"] = visual
1327
+ return result
1328
+ except Exception as e:
1329
+ logging.error(f"Wound analysis error: {e}")
1330
+ return {
1331
+ "success": False,
1332
+ "error": str(e),
1333
+ "visual_analysis": {},
1334
+ "report": f"Analysis initialization failed: {str(e)}",
1335
+ "saved_image_path": None,
1336
+ "guideline_context": "",
1337
+ }