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 package org.openimaj.image.analysis.watershed;
31
32 import java.util.ArrayDeque;
33 import java.util.ArrayList;
34 import java.util.BitSet;
35 import java.util.List;
36
37 import org.openimaj.image.FImage;
38 import org.openimaj.image.analysis.watershed.event.ComponentStackMergeListener;
39 import org.openimaj.image.analysis.watershed.feature.ComponentFeature;
40 import org.openimaj.image.pixel.IntValuePixel;
41
42
43
44
45
46
47
48
49
50 public class WatershedProcessorAlgorithm
51 {
52
53
54
55
56
57
58
59
60 private class BoundaryHeap
61 {
62 private BitSet availablePixels;
63 private ArrayDeque<IntValuePixel>[] stacks;
64
65
66
67
68
69
70
71
72 @SuppressWarnings("unchecked")
73 public BoundaryHeap(int sz) {
74 availablePixels = new BitSet(sz);
75 stacks = new ArrayDeque[sz];
76
77 for (int i = 0; i < sz; i++)
78 stacks[i] = new ArrayDeque<IntValuePixel>();
79 }
80
81
82
83
84
85
86
87 public void push(IntValuePixel p)
88 {
89 final ArrayDeque<IntValuePixel> l = stacks[p.value];
90 l.push(p);
91 availablePixels.set(p.value);
92 }
93
94
95
96
97
98
99
100
101
102 public IntValuePixel pop()
103 {
104 final int l = availablePixels.nextSetBit(0);
105 if (l == -1)
106 return null;
107
108 final IntValuePixel xx = this.stacks[l].pop();
109 if (this.stacks[l].size() == 0)
110 availablePixels.set(l, false);
111 return xx;
112 }
113 }
114
115
116 private IntValuePixel startPixel = null;
117
118
119 private BitSet accessibleMask = null;
120
121
122 private IntValuePixel currentPixel = null;
123
124
125 private ArrayDeque<Component> componentStack = null;
126
127
128 private BoundaryHeap boundaryHeap = null;
129
130
131 private int[][] greyscaleImage = null;
132
133
134
135
136
137 private List<ComponentStackMergeListener> csmListeners = null;
138
139 private Class<? extends ComponentFeature>[] featureClasses;
140
141
142
143
144
145
146
147
148
149
150
151
152 @SafeVarargs
153 public WatershedProcessorAlgorithm(int[][] greyscaleImage, IntValuePixel startPixel,
154 Class<? extends ComponentFeature>... featureClasses)
155 {
156 this.greyscaleImage = greyscaleImage;
157 this.startPixel = startPixel;
158 this.csmListeners = new ArrayList<ComponentStackMergeListener>();
159
160 this.featureClasses = featureClasses;
161 }
162
163
164
165
166
167
168
169
170
171
172
173
174 @SafeVarargs
175 public WatershedProcessorAlgorithm(FImage bGreyscaleImage, IntValuePixel startPixel,
176 Class<? extends ComponentFeature>... featureClasses)
177 {
178 this(new int[bGreyscaleImage.getHeight()][bGreyscaleImage.getWidth()], startPixel, featureClasses);
179
180 for (int j = 0; j < bGreyscaleImage.getHeight(); j++) {
181 for (int i = 0; i < bGreyscaleImage.getWidth(); i++) {
182 greyscaleImage[j][i] = (int) (bGreyscaleImage.pixels[j][i] * 255);
183 }
184 }
185 }
186
187
188
189
190
191
192 public void startPour()
193 {
194
195
196 this.currentPixel = startPixel;
197
198
199 this.currentPixel.value = greyscaleImage[this.startPixel.y][this.startPixel.x];
200
201
202 this.accessibleMask = new BitSet(this.greyscaleImage.length * this.greyscaleImage[0].length);
203
204
205 this.componentStack = new ArrayDeque<Component>();
206
207
208 this.boundaryHeap = new BoundaryHeap(256);
209
210
211
212 final Component dummyComponent = new Component(new IntValuePixel(-1, -1, Integer.MAX_VALUE), featureClasses);
213 this.componentStack.push(dummyComponent);
214
215
216 this.processNeighbours();
217
218
219 }
220
221
222
223
224 private void processNeighbours()
225 {
226
227
228 Component currentComponent = new Component(this.currentPixel, featureClasses);
229 componentStack.push(currentComponent);
230
231
232
233 final boolean processNeighbours = true;
234 while (processNeighbours)
235 {
236 boolean toContinue = false;
237
238
239 final IntValuePixel[] neighbours = getNeighbourPixels_4(this.currentPixel);
240
241
242
243
244 for (final IntValuePixel neighbour : neighbours)
245 {
246 if (neighbour == null)
247 break;
248
249
250 final int idx = neighbour.x + neighbour.y * this.greyscaleImage[0].length;
251
252
253 if (!this.accessibleMask.get(idx))
254 {
255
256 this.accessibleMask.set(idx);
257
258
259
260 if (neighbour.value >= currentPixel.value)
261 {
262
263 this.boundaryHeap.push(neighbour);
264
265
266 }
267
268
269
270
271
272
273 else
274 {
275 this.boundaryHeap.push(currentPixel);
276
277
278 this.currentPixel = neighbour;
279 currentComponent = new Component(this.currentPixel, featureClasses);
280 componentStack.push(currentComponent);
281 toContinue = true;
282 break;
283 }
284 }
285 }
286
287 if (toContinue)
288 continue;
289
290
291
292 this.componentStack.peek().accumulate(this.currentPixel);
293
294
295
296
297 final IntValuePixel p = this.boundaryHeap.pop();
298
299
300
301 if (p == null)
302 return;
303
304
305 if (p.value == currentPixel.value)
306 {
307 this.currentPixel = p;
308 }
309
310
311 else
312 {
313 this.currentPixel = p;
314 processComponentStack();
315 }
316 }
317 }
318
319 private void processComponentStack()
320 {
321 while (this.currentPixel.value > this.componentStack.peek().pivot.value)
322 {
323
324
325
326
327
328 final Component topOfStack = this.componentStack.pop();
329
330
331
332
333
334
335 if (this.currentPixel.value < this.componentStack.peek().pivot.value)
336 {
337 topOfStack.pivot = this.currentPixel;
338 this.componentStack.push(topOfStack);
339
340 fireComponentStackMergeListener(componentStack.peek());
341
342 return;
343 }
344
345 fireComponentStackMergeListener(componentStack.peek(), topOfStack);
346
347
348
349 this.componentStack.peek().merge(topOfStack);
350
351
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364 private IntValuePixel[] getNeighbourPixels_4(IntValuePixel pixel)
365 {
366 final IntValuePixel[] p = new IntValuePixel[4];
367 final int x = pixel.x;
368 final int y = pixel.y;
369
370 final int height = this.greyscaleImage.length;
371 final int width = this.greyscaleImage[0].length;
372
373
374 int c = 0;
375
376 if (x < width - 1)
377 p[c++] = new IntValuePixel(x + 1, y, greyscaleImage[y][x + 1]);
378
379 if (x > 0)
380 p[c++] = new IntValuePixel(x - 1, y, greyscaleImage[y][x - 1]);
381
382 if (y < height - 1)
383 p[c++] = new IntValuePixel(x, y + 1, greyscaleImage[y + 1][x]);
384
385 if (y > 0)
386 p[c++] = new IntValuePixel(x, y - 1, greyscaleImage[y - 1][x]);
387
388 return p;
389 }
390
391
392
393
394
395
396
397 public void addComponentStackMergeListener(ComponentStackMergeListener csml)
398 {
399 csmListeners.add(csml);
400 }
401
402
403
404
405
406
407
408
409 public void removeComponentStackMergeListener(ComponentStackMergeListener csml)
410 {
411 csmListeners.remove(csml);
412 }
413
414
415
416
417
418
419
420
421
422
423 private void fireComponentStackMergeListener(Component c1, Component c2)
424 {
425 for (final ComponentStackMergeListener csm : csmListeners)
426 csm.componentsMerged(c1, c2);
427 }
428
429
430
431
432
433
434
435
436
437 private void fireComponentStackMergeListener(Component c1)
438 {
439 for (final ComponentStackMergeListener csm : csmListeners)
440 csm.componentPromoted(c1);
441 }
442
443
444
445
446
447
448
449 @SuppressWarnings("unused")
450 private String outputArray(Object[] o)
451 {
452 final StringBuilder sb = new StringBuilder();
453 sb.append("[");
454 boolean first = true;
455 for (final Object obj : o)
456 {
457 if (!first)
458 sb.append(",");
459 if (obj == null)
460 sb.append("null");
461 else
462 sb.append(obj.toString());
463 first = false;
464 }
465 sb.append("]");
466 return sb.toString();
467 }
468 }