-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathlabel_coins.py
More file actions
100 lines (81 loc) · 3.58 KB
/
Copy pathlabel_coins.py
File metadata and controls
100 lines (81 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# Note: don't enforce certain Python versions in reality;
# this is just to help learn how to install different Pyhton versions when needed.
import sys
if sys.version_info.major != 3 or sys.version_info.minor != 11:
raise RuntimeError("This script should be run by Python 3.11.")
import napari
import numpy as np
from skimage.data import coins
from skimage.segmentation import flood
from skimage.measure import label, regionprops
from skimage.draw import disk
# Global set to track which seed points have been processed.
processed_points = set()
def detect_coin_disk(image, point, tolerance=200):
"""
Given a grayscale image and a seed point (row, col), this function:
1. Uses region-growing (flood) segmentation with the given tolerance to capture
the coin’s connected region.
2. Labels the region and identifies the region that contains the seed point.
3. Computes the region’s centroid and equivalent diameter.
4. Creates a disk mask (via skimage.draw.disk) centered at the computed centroid,
with a radius of half the equivalent diameter.
Returns a boolean mask (same shape as the image) where the fitted disk is True.
If segmentation fails or no region is found, returns None.
"""
y, x = int(round(point[0])), int(round(point[1]))
# Use flood segmentation to capture the coin region around the seed point.
try:
coin_mask = flood(image, (y, x), tolerance=tolerance)
except Exception:
return None
if not coin_mask.any():
return None
# Label the segmented region.
labeled = label(coin_mask)
# Identify the region that contains the seed.
selected_region = None
for region in regionprops(labeled):
minr, minc, maxr, maxc = region.bbox
if minr <= y < maxr and minc <= x < maxc:
selected_region = region
break
if selected_region is None:
return None
# Use the region’s centroid and equivalent diameter to define the disk.
centroid = selected_region.centroid # (y, x) in float coordinates
eq_diam = selected_region.equivalent_diameter
radius = int(round(eq_diam / 2))
# Create a disk mask on a full-image canvas.
disk_mask = np.zeros_like(image, dtype=bool)
rr, cc = disk((int(round(centroid[0])), int(round(centroid[1]))), radius, shape=image.shape)
disk_mask[rr, cc] = True
return disk_mask
def on_points_change(event):
global processed_points
# Loop over all points in the points layer.
for i, point in enumerate(points_layer.data):
if i in processed_points:
continue
# Compute the best-fit disk for the coin region around the seed.
disk_mask = detect_coin_disk(image_data, point, tolerance=30)
if disk_mask is not None:
# Label the fitted disk in the labels layer (label index = seed index + 1).
labels_data[disk_mask] = i + 1
labels_layer.data = labels_data # update display
processed_points.add(i)
# Load the coins image.
image_data = coins()
# Create a napari viewer.
viewer = napari.Viewer()
# Add the coins image layer.
image_layer = viewer.add_image(image_data, name='coins')
# Create an empty labels layer (same shape as image, integer type).
labels_data = np.zeros_like(image_data, dtype=int)
labels_layer = viewer.add_labels(labels_data, name='labels')
# Add an empty points layer.
points_layer = viewer.add_points(np.empty((0, 2)), name='points')
# Connect the points layer event to our callback.
points_layer.events.data.connect(on_points_change)
# Start the napari event loop.
napari.run()