1
1
import Base. isless
2
2
3
- struct PixelKey{CT}
3
+ struct PixelKey{CT, N }
4
4
val:: CT
5
5
time_step:: Int
6
+ source:: CartesianIndex{N}
6
7
end
7
- isless (a:: PixelKey{T} , b:: PixelKey{T} ) where {T} = (a. val < b. val) || (a. val == b. val && a. time_step < b. time_step)
8
+ isless (a:: PixelKey , b:: PixelKey ) = (a. val < b. val) || (a. val == b. val && a. time_step < b. time_step)
9
+
10
+ """ Calculate the euclidean distance between two `CartesianIndex` structs"""
11
+ @inline _euclidean (a:: CartesianIndex{N} , b:: CartesianIndex{N} ) where {N} = sqrt (sum (Tuple (a - b) .^ 2 ))
8
12
9
13
"""
10
14
```
11
- segments = watershed(img, markers)
15
+ segments = watershed(img, markers; compactness, mask )
12
16
```
13
17
Segments the image using watershed transform. Each basin formed by watershed transform corresponds to a segment.
14
18
If you are using image local minimas as markers, consider using [`hmin_transform`](@ref) to avoid oversegmentation.
@@ -18,18 +22,42 @@ Parameters:
18
22
- markers = An array (same size as img) with each region's marker assigned a index starting from 1. Zero means not a marker.
19
23
If two markers have the same index, their regions will be merged into a single region.
20
24
If you have markers as a boolean array, use `label_components`.
25
+ - compactness = Use the compact watershed algorithm with the given compactness parameter. Larger values lead to more regularly
26
+ shaped watershed basins.[^1]
27
+ - mask = Only segment pixels where the value of `mask` is true, used to restrict segmentation to only areas of interest
28
+
29
+
30
+ [^1]: https://www.tu-chemnitz.de/etit/proaut/publications/cws_pSLIC_ICPR.pdf
31
+
32
+ # Example
33
+
34
+ ```jldoctest; setup = :(using Images, ImageSegmentation)
35
+ julia> seeds = falses(100, 100); seeds[50, 25] = true; seeds[50, 75] = true;
21
36
37
+ julia> dists = distance_transform(feature_transform(seeds)); # calculate distances from seeds
22
38
39
+ julia> markers = label_components(seeds); # give each seed a unique integer id
23
40
41
+ julia> results = watershed(dists, markers);
42
+
43
+ julia> labels_map(result); # labels of segmented image
44
+ ```
24
45
"""
25
- function watershed (img:: AbstractArray{T, N} , markers:: AbstractArray{S,N} ) where {T<: Images.NumberLike , S<: Integer , N}
46
+ function watershed (img:: AbstractArray{T, N} ,
47
+ markers:: AbstractArray{S,N} ;
48
+ mask:: AbstractArray{Bool, N} = fill (true , axes (img)),
49
+ compactness:: Real = 0.0 ) where {T<: Images.NumberLike , S<: Integer , N}
26
50
27
51
if axes (img) != axes (markers)
28
52
error (" image size doesn't match marker image size" )
53
+ elseif axes (img) != axes (mask)
54
+ error (" image size doesn't match mask size" )
29
55
end
30
56
57
+ compact = compactness > 0.0
31
58
segments = copy (markers)
32
- pq = PriorityQueue {CartesianIndex{N}, PixelKey{T}} ()
59
+ PK = PixelKey{compact ? floattype (T) : T, N}
60
+ pq = PriorityQueue {CartesianIndex{N}, PK} ()
33
61
time_step = 0
34
62
35
63
R = CartesianIndices (axes (img))
@@ -39,22 +67,63 @@ function watershed(img::AbstractArray{T, N}, markers::AbstractArray{S,N}) where
39
67
for j in CartesianIndices (_colon (max (Istart,i- one (i)), min (i+ one (i),Iend)))
40
68
if segments[j] == 0
41
69
segments[j] = markers[i]
42
- enqueue! (pq, j, PixelKey (img[i], time_step))
70
+ enqueue! (pq, j, PK (img[i], time_step, j ))
43
71
time_step += 1
44
72
end
45
73
end
46
74
end
47
75
end
48
76
49
77
while ! isempty (pq)
50
- current = dequeue! (pq)
51
- segments_current = segments[current]
52
- img_current = img[current]
53
- for j in CartesianIndices (_colon (max (Istart,current- one (current)), min (current+ one (current),Iend)))
78
+ curr_idx, curr_elem = dequeue_pair! (pq)
79
+ segments_current = segments[curr_idx]
80
+
81
+ # If we're using the compact algorithm, we need assign grouping for a given location
82
+ # when it comes off the queue since we could find a better suited watershed later.
83
+ if compact
84
+ if segments_current > 0 && curr_idx != curr_elem. source
85
+ # this is a non-marker location that we've already assigned
86
+ continue
87
+ end
88
+ # group this location with its watershed
89
+ segments[curr_idx] = segments[curr_elem. source]
90
+ end
91
+
92
+ img_current = img[curr_idx]
93
+ for j in CartesianIndices (_colon (max (Istart,curr_idx- one (curr_idx)), min (curr_idx+ one (curr_idx),Iend)))
94
+
95
+ # if this location is false in the mask, we skip it
96
+ (! mask[j]) && continue
97
+ # only continue if this is a position that we haven't assigned yet
54
98
if segments[j] == 0
55
- segments[j] = segments_current
56
- enqueue! (pq, j, PixelKey (img_current, time_step))
57
- time_step += 1
99
+ # if we're doing a simple watershed, we can go ahead and set the final grouping for a new
100
+ # ungrouped position the moment we first encounter it
101
+ if ! compact
102
+ segments[j] = segments_current
103
+ new_value = img_current
104
+ else
105
+ # in the compact algorithm case, we don't set the grouping
106
+ # at push-time and instead calculate a temporary value based
107
+ # on the weighted sum of the intensity and distance to the
108
+ # current source marker.
109
+ new_value = floattype (T)(img_current + compactness * _euclidean (j, curr_elem. source))
110
+ end
111
+
112
+ # if this position is in the queue and we're using the compact algorithm, we need to replace
113
+ # its watershed if we find one that it better belongs to
114
+ if compact && j in keys (pq)
115
+ elem = pq[j]
116
+ new_elem = PK (new_value, time_step, curr_elem. source)
117
+
118
+ if new_elem < elem
119
+ pq[j] = new_elem # update the watershed
120
+ time_step += 1
121
+ end
122
+ else
123
+
124
+ pq[j] = PK (new_value, time_step, curr_elem. source)
125
+ time_step += 1
126
+ end
58
127
end
59
128
end
60
129
end
0 commit comments