Boundary-Radial Persistence Loss / mini_segmentation.py
Mechanism confirmed, baseline not beaten
1import json
2import numpy as np
3from scipy.ndimage import binary_erosion, label
4from boundary_radial import radial_loss
5
6
7def disk(shape, center, radius):
8 yy, xx = np.indices(shape)
9 return ((xx-center[0])**2 + (yy-center[1])**2 <= radius**2)
10
11
12def boundary_component_radii(mask, center):
13 """Extract connected one-pixel boundaries and summarize each by median radius."""
14 boundary = mask & ~binary_erosion(mask, structure=np.ones((3, 3), bool))
15 lab, n = label(boundary, structure=np.ones((3, 3), bool))
16 yy, xx = np.indices(mask.shape)
17 out = []
18 for k in range(1, n+1):
19 pix = lab == k
20 out.append(float(np.median(np.sqrt((xx[pix]-center[0])**2 +
21 (yy[pix]-center[1])**2))))
22 return sorted(out)
23
24
25def dice(a, b):
26 den = int(a.sum() + b.sum())
27 return float(2*(a & b).sum()/den) if den else 1.0
28
29
30def main():
31 shape, center = (64, 64), (32, 32)
32 target = disk(shape, center, 14)
33 shifted = disk(shape, (35, 32), 14)
34 # Put the extra component away from the target but retain a comparable area.
35 extra = target | disk(shape, (51, 48), 5)
36 rows = []
37 for name, pred in [('shifted', shifted), ('extra_component', extra)]:
38 tr = boundary_component_radii(target, center)
39 pr = boundary_component_radii(pred, center)
40 rows.append({'case': name, 'dice': dice(pred, target),
41 'target_radii': tr, 'pred_radii': pr,
42 'radial_loss_lambda_10': radial_loss(pr, tr, unmatched=10.0)})
43 result = {'center': center, 'rows': rows}
44 print(json.dumps(result, indent=2))
45 with open('segmentation_results.json', 'w') as f:
46 json.dump(result, f, indent=2)
47
48
49if __name__ == '__main__':
50 main()