File: Wand.java

package info (click to toggle)
imagej 1.54g-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,520 kB
  • sloc: java: 132,209; sh: 286; xml: 255; makefile: 6
file content (353 lines) | stat: -rw-r--r-- 16,128 bytes parent folder | download | duplicates (4)
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
package ij.gui;
import ij.*;
import ij.process.*;
import ij.plugin.WandToolOptions;
import java.awt.*;

/** This class implements ImageJ's wand (tracing) tool.
  * The wand selects pixels of equal or similar value or thresholded pixels
  * forming a contiguous area.
  * The wand creates selections that have only one boundary line (inner holes
  * are not excluded from the selection). There may be holes at the boundary,
  * however, if the boundary line touches the same vertex twice (both in
  * 4-connected and 8-connected mode).
  *
  * Version 2009-06-01  (code refurbished; tolerance, 4- & 8-connected options added)
  */
public class Wand {
    /** Wand operation type: trace outline of 4-connected pixels */
    public final static int FOUR_CONNECTED = 4;
    /** Wand operation type: trace outline of 8-connected pixels */
    public final static int EIGHT_CONNECTED = 8;
    /** Wand operation type similar to  that of ImageJ 1.42p and before; for backwards
      * compatibility.
      * In this mode, no checking is done whether the foreground or the background
      * gets selected; four- or 8-connected behaviour depends on foreground/background
      * and (if no selection) on whether the initial pixel is on a 1-pixel wide line. */
    public final static int LEGACY_MODE = 1;    
    /** The number of points in the generated outline. */
    public int npoints;
    private int maxPoints = 1000; // will be increased if necessary
    /** The x-coordinates of the points in the outline.
        A vertical boundary at x separates the pixels at x-1 and x. */
    public int[] xpoints = new int[maxPoints];
    /** The y-coordinates of the points in the outline.
        A horizontal boundary at y separates the pixels at y-1 and y. */
    public int[] ypoints = new int[maxPoints];

    private final static int THRESHOLDED_MODE = 256; //work on threshold
    private ImageProcessor ip;
    private byte[] bpixels;
    private int[] cpixels;
    private short[] spixels;
    private float[] fpixels;
    private int width, height;
    private float lowerThreshold, upperThreshold;
    private int xmin;                   // of selection created
    private boolean exactPixelValue;    // For color, match RGB, not gray value
    private static boolean allPoints;  // output contains intermediate points


    /** Constructs a Wand object from an ImageProcessor. */
    public Wand(ImageProcessor ip) {
        this.ip = ip;
        Object pixels = ip.getPixels();
        if (pixels instanceof byte[])
            bpixels = (byte[])pixels;
        else if (pixels instanceof int[])
            cpixels = (int[])pixels;
        else if (pixels instanceof short[])
            spixels = (short[])pixels;
        else if (pixels instanceof float[])
            fpixels = (float[])pixels;
        width = ip.getWidth();
        height = ip.getHeight();
    }



    /** Traces an object defined by lower and upper threshold values.
      * 'mode' can be FOUR_CONNECTED or EIGHT_CONNECTED.
      * ('LEGACY_MODE' is also supported and may result in selection of
      * interior holes instead of the thresholded area if one clicks left
      * of an interior hole).
      * The start coordinates must be inside the area or left of it.
      * When successful, npoints>0 and the boundary points can be accessed
      * in the public xpoints and ypoints fields. */
    public void autoOutline(int startX, int startY, double lower, double upper, int mode) {
        lowerThreshold = (float)lower;
        upperThreshold = (float)upper;
        autoOutline(startX, startY, 0.0, mode|THRESHOLDED_MODE);
    }

    /** Traces an object defined by lower and upper threshold values or an
      * interior hole; whatever is found first ('legacy mode').
      * For compatibility with previous versions of ImageJ.
      * The start coordinates must be inside the area or left of it.
      * When successful, npoints>0 and the boundary points can be accessed
      * in the public xpoints and ypoints fields. */
    public void autoOutline(int startX, int startY, double lower, double upper) {
        autoOutline(startX, startY, lower, upper, THRESHOLDED_MODE|LEGACY_MODE);
    }

    /** This is a variation of legacy autoOutline that uses int threshold arguments. */
    public void autoOutline(int startX, int startY, int lower, int upper) {
        autoOutline(startX, startY, (double)lower, (double)upper, THRESHOLDED_MODE|LEGACY_MODE);
    }

    /** Traces the boundary of an area of uniform color, where
      * 'startX' and 'startY' are somewhere inside the area.
      * When successful, npoints>0 and the boundary points can be accessed
      * in the public xpoints and ypoints fields.
      * For compatibility with previous versions of ImageJ only; otherwise
      * use the reliable method specifying 4-connected or 8-connected mode
      * and the tolerance. */
    public void autoOutline(int startX, int startY) {
        autoOutline(startX, startY, 0.0, LEGACY_MODE);
    }

    /** Traces the boundary of the area with pixel values within
      * 'tolerance' of the value of the pixel at the starting location.
      * 'tolerance' is in uncalibrated units.
      * 'mode' can be FOUR_CONNECTED or EIGHT_CONNECTED.
      * Mode LEGACY_MODE is for compatibility with previous versions of ImageJ;
      * ignored if tolerance > 0.
      * Mode bit THRESHOLDED_MODE for internal use only; it is set by autoOutline
      * with 'upper' and 'lower' arguments.
      * When successful, npoints>0 and the boundary points can be accessed
      * in the public xpoints and ypoints fields. */
    public void autoOutline(int startX, int startY, double tolerance, int mode) {
        if (startX<0 || startX>=width || startY<0 || startY>=height) return;
        if (fpixels!=null && Float.isNaN(getPixel(startX, startY))) return;
        WandToolOptions.setStart(startX, startY);
        exactPixelValue = tolerance==0;
        boolean thresholdMode = (mode & THRESHOLDED_MODE) != 0;
        boolean legacyMode = (mode & LEGACY_MODE) != 0 && tolerance == 0;
        if (!thresholdMode) {
            double startValue = getPixel(startX, startY);
            lowerThreshold = (float)(startValue - tolerance);
            upperThreshold = (float)(startValue + tolerance);
        }
        int x = startX;
        int y = startY;
        int seedX;                      // the first inside pixel
        if (inside(x,y)) {              // find a border when coming from inside
            seedX = x;                // (seedX, startY) is an inside pixel
            do {x++;} while (inside(x,y));
        } else {                        // find a border when coming from outside (thresholded only)
            do {
                x++;
                if (x>=width) return;   // no border found
            } while (!inside(x,y));
            seedX = x;
        }
        boolean fourConnected;
        if (legacyMode)
            fourConnected = !thresholdMode && !(isLine(x, y));
        else
            fourConnected = (mode & FOUR_CONNECTED) != 0;
        //now, we have a border between (x-1, y) and (x,y)
        boolean first = true;
        while (true) {                  // loop until we have not traced an inner hole
            boolean insideSelected = traceEdge(x, y, fourConnected);
            if (legacyMode) return;     // in legacy mode, don't care what we have got
            if (insideSelected) {       // not an inner hole
                if (first) return;      // started at seed, so we got it (sucessful)
                if (xmin<=seedX) {      // possibly the correct particle
                    Polygon poly = new Polygon(xpoints, ypoints, npoints);
                    if (poly.contains(seedX, startY))
                        return;         // successful, particle contains seed
                }
            }
            first = false;
            // we have traced an inner hole or the wrong particle
            if (!inside(x,y)) do {
                x++;                    // traverse the hole
                if (x>width) throw new RuntimeException("Wand Malfunction"); //should never happen
            } while (!inside(x,y));
            do {x++;} while (inside(x,y)); //retry here; maybe no inner hole any more
        }
    }


    /* Trace the outline, starting at a point (startX, startY). 
     * Pixel (startX-1, startY) must be outside, (startX, startY) must be inside,
     * or reverse. Otherwise an endless loop will occur (and eat up all memory).
     * Traces 8-connected inside pixels unless fourConnected is true.
     * Returns whether the selection created encloses an 'inside' area
     * and not an inner hole.
     */
    private boolean traceEdge(int startX, int startY, boolean fourConnected) {
        // Let us name the crossings between 4 pixels vertices, then the
        // vertex (x,y) marked with '+', is between pixels (x-1, y-1) and (x,y):
        //
        //    pixel    x-1    x
        //      y-1        |
        //             ----+----
        //       y         |
        //
        // The four principal directions are numbered such that the direction
        // number * 90 degrees gives the angle in the mathematical sense; and
        // the directions to the adjacent pixels (for inside(x,y,direction) are
        // at (number * 90 - 45) degrees:
        //      walking                     pixel
        //   directions:   1           directions:     2 | 1
        //              2  +  0                      ----+----
        //                 3                           3 | 0
        //
        // Directions, like angles, are cyclic; direction -1 = direction 3, etc.
        //
        // The algorithm: We walk along the border, from one vertex to the next,
        // with the outside pixels always being at the left-hand side.
        // For 8-connected tracing, we always trying to turn left as much as
        // possible, to encompass an area as large as possible.
        // Thus, when walking in direction 1 (up, -y), we start looking
        // at the pixel in direction 2; if it is inside, we proceed in this
        // direction (left); otherwise we try with direction 1 (up); if pixel 1
        // is not inside, we must proceed in direction 0 (right).
        //
        //                     2 | 1                 (i=inside, o=outside)
        //      direction 2 < ---+---- > direction 0
        //                     o | i
        //                       ^ direction 1 = up = starting direction
        //
        // For 4-connected pixels, we try to go right as much as possible:
        // First try with pixel 1; if it is outside we go in direction 0 (right).
        // Otherwise, we examine pixel 2; if it is outside, we go in
        // direction 1 (up); otherwise in direction 2 (left).
        //
        // When moving a closed loop, 'direction' gets incremented or decremented
        // by a total of 360 degrees (i.e., 4) for counterclockwise and clockwise
        // loops respectively. As the inside pixels are at the right side, we have
        // got an outline of inner pixels after a cw loop (direction decremented
        // by 4).
        //
        npoints = 0;
        xmin = width;
        final int startDirection;
        if (inside(startX,startY))      // inside at left, outside right
            startDirection = 1;         // starting in direction 1 = up
        else {
            startDirection = 3;         // starting in direction 3 = down
            startY++;                   // continue after the boundary that has direction 3
        }
        int x = startX;
        int y = startY;
        int direction = startDirection;
        do {
            int newDirection;
            if (fourConnected) {
                newDirection = direction;
                do {
                    if (!inside(x, y, newDirection)) break;
                    newDirection++;
                } while (newDirection < direction+2);
                newDirection--;
            } else { // 8-connected
                newDirection = direction + 1;
                do {
                if (inside(x, y, newDirection)) break;
                    newDirection--;
                } while (newDirection >= direction);
            }
            if (allPoints || newDirection!=direction)
                addPoint(x,y);          // a corner point of the outline polygon: add to list
            switch (newDirection & 3) { // '& 3' is remainder modulo 4
                case 0: x++; break;
                case 1: y--; break;
                case 2: x--; break;
                case 3: y++; break;
            }
            direction = newDirection;
        } while (x!=startX || y!=startY || (direction&3)!=startDirection);
        if (xpoints[0]!=x && !allPoints)            // if the start point = end point is a corner: add to list
            addPoint(x, y);
        return (direction <= 0);        // if we have done a clockwise loop, inside pixels are enclosed
    }

    // add a point x,y to the outline polygon
    private void addPoint (int x, int y) {
        if (npoints==maxPoints) {
            int[] xtemp = new int[maxPoints*2];
            int[] ytemp = new int[maxPoints*2];
            System.arraycopy(xpoints, 0, xtemp, 0, maxPoints);
            System.arraycopy(ypoints, 0, ytemp, 0, maxPoints);
            xpoints = xtemp;
            ypoints = ytemp;
            maxPoints *= 2;
        }
        xpoints[npoints] = x;
        ypoints[npoints] = y;
        npoints++;
        if (xmin > x) xmin = x;
    }

    // check pixel at (x,y), whether it is inside traced area
    private boolean inside(int x, int y) {
        if (x<0 || x>=width || y<0 || y>=height)
            return false;
        float value = getPixel(x, y);
        return value>=lowerThreshold && value<=upperThreshold;
    }

    // check pixel in a given direction from vertex (x,y)
    private boolean inside(int x, int y, int direction) {
        switch(direction & 3) {         // '& 3' is remainder modulo 4
            case 0: return inside(x, y);
            case 1: return inside(x, y-1);
            case 2: return inside(x-1, y-1);
            case 3: return inside(x-1, y);
        }
        return false; //will never occur, needed for the compiler
    }

    // get a pixel value; returns Float.NaN if outside the field.
    private float getPixel(int x, int y) {
        if (x<0 || x>=width || y<0 || y>=height)
            return Float.NaN;
        if (bpixels!=null)
            return bpixels[y*width + x] & 0xff;
        else if (spixels!=null)
            return spixels[y*width + x] & 0xffff;
        else if (fpixels!=null)
            return fpixels[y*width + x];
        else if (exactPixelValue)   //RGB for exact match
            return cpixels[y*width + x] & 0xffffff; //don't care for upper byte
        else                      //gray value of RGB
            return ip.getPixelValue(x,y);
    }

    /* Are we tracing a one pixel wide line? Makes Legacy mode 8-connected instead of 4-connected */
    private boolean isLine(int xs, int ys) {
        int r = 5;
        int xmin=xs;
        int xmax=xs+2*r;
        if (xmax>=width) xmax=width-1;
        int ymin=ys-r;
        if (ymin<0) ymin=0;
        int ymax=ys+r;
        if (ymax>=height) ymax=height-1;
        int area = 0;
        int insideCount = 0;
        for (int x=xmin; (x<=xmax); x++)
            for (int y=ymin; y<=ymax; y++) {
                area++;
                if (inside(x,y))
                    insideCount++;
            }
        if (IJ.debugMode)
            IJ.log((((double)insideCount)/area<0.25?"line ":"blob ")+insideCount+" "+area+" "+IJ.d2s(((double)insideCount)/area));
        return ((double)insideCount)/area<0.25;
    }

    /** Set 'true' and output will contain intermediate points for straight lines longer than one pixel. */
    public static void setAllPoints(boolean b) {
        allPoints = b;
    }

	/** Returns 'true' if output contains intermediate points for straight lines longer than one pixel. */
	public static boolean allPoints() {
		return allPoints;
	}

}