Organ-aware 3D lesion segmentation dataset and pipeline for abdominal CT analysis (ACM Multimedia 2025 candidate)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2D_results.py 2.9KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import os
  2. import numpy as np
  3. import cv2
  4. from tqdm import tqdm
  5. from sklearn.metrics import jaccard_score, f1_score
  6. import pandas as pd
  7. from scipy.ndimage import distance_transform_edt
  8. def extract_border(mask):
  9. """Extract binary border from mask using morphological gradient."""
  10. kernel = np.ones((3, 3), dtype=np.uint8)
  11. dilated = cv2.dilate(mask, kernel, iterations=1)
  12. eroded = cv2.erode(mask, kernel, iterations=1)
  13. border = dilated - eroded
  14. return border
  15. def surface_dice(gt_mask, pred_mask, tolerance=2):
  16. gt_border = extract_border((gt_mask > 0).astype(np.uint8))
  17. pred_border = extract_border((pred_mask > 0).astype(np.uint8))
  18. if not np.any(gt_border) or not np.any(pred_border):
  19. return np.nan
  20. gt_dist = distance_transform_edt(1 - gt_border)
  21. pred_dist = distance_transform_edt(1 - pred_border)
  22. gt_match = pred_dist[gt_border > 0] <= tolerance
  23. pred_match = gt_dist[pred_border > 0] <= tolerance
  24. tp = np.count_nonzero(gt_match) + np.count_nonzero(pred_match)
  25. total = np.count_nonzero(gt_border) + np.count_nonzero(pred_border)
  26. return tp / total if total > 0 else np.nan
  27. def compute_metrics(gt_folder, pred_folder, output_csv="results.csv", tolerance=2):
  28. metrics = {
  29. "filename": [],
  30. "IoU": [],
  31. "Dice": [],
  32. "SurfaceDice": []
  33. }
  34. pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith(".png")])
  35. for filename in tqdm(pred_files):
  36. pred_path = os.path.join(pred_folder, filename)
  37. gt_path = os.path.join(gt_folder, filename)
  38. print(pred_path,gt_path)
  39. if not os.path.exists(gt_path):
  40. print(f"⚠️ There is no {filename}")
  41. continue
  42. gt_mask = cv2.imread(gt_path, cv2.IMREAD_GRAYSCALE)
  43. pred_mask = cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE)
  44. if gt_mask is None or pred_mask is None or gt_mask.shape != pred_mask.shape:
  45. print(f"⚠️ No file : {filename}")
  46. continue
  47. gt_bin = (gt_mask > 0).astype(np.uint8).flatten()
  48. pred_bin = (pred_mask > 0).astype(np.uint8).flatten()
  49. iou = jaccard_score(gt_bin, pred_bin, zero_division=0)
  50. dice = f1_score(gt_bin, pred_bin, zero_division=0)
  51. surf_dice = surface_dice(gt_mask, pred_mask, tolerance)
  52. metrics["filename"].append(filename)
  53. metrics["IoU"].append(iou)
  54. metrics["Dice"].append(dice)
  55. metrics["SurfaceDice"].append(surf_dice)
  56. df = pd.DataFrame(metrics)
  57. if len(df) > 0:
  58. mean_row = {
  59. "filename": "MEAN",
  60. "IoU": df["IoU"].mean(),
  61. "Dice": df["Dice"].mean(),
  62. "SurfaceDice": df["SurfaceDice"].mean()
  63. }
  64. df = pd.concat([df, pd.DataFrame([mean_row])], ignore_index=True)
  65. df.to_csv(output_csv, index=False)
  66. compute_metrics(
  67. gt_folder="",
  68. pred_folder="",
  69. output_csv="",
  70. tolerance=2
  71. )