Boundary-Radial Persistence Loss / mini_segmentation.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
 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()