package fr.unistra.pelican.util.snake; import java.awt.Point; import java.awt.geom.Line2D; import java.util.ArrayList; import java.util.Arrays; import fr.unistra.pelican.Image; /** * * Snake (active contour) model based on the greedy algorithm * * @author lefevre * */ public class Snake { private int size; private Point[] points; private double averageDistance; // Energy Continuity private double normalx, normaly; // Energy Balloon private Point shifted = new Point(); // Energy Gradient private int color[]; // Energy Color private boolean deltaGradient[]; // External splitting private boolean deltaBackground[]; // External splitting private Image gradient; // Energy Gradient private Image reference; // Energy Background private Image data; // Energy Color private boolean colorContour = true; // Use only contour points to // compute // color private double epsilon = 0.01; // Normalisation epsilon private int zoom = 0; // Zooming factor private int sizeNeighbourhood = 5; // Neighbourhood size private boolean normaliseNeighbourhood = true; private boolean lengthCriteria = true; private boolean partialImage = false; // Coefficients for the different energies private double coeffContinuity = 1; private double coeffBalloon = 1; private double coeffCurvature = 1; private double coeffGradient = 1; private double coeffBackground = 1; private double coeffIntern = 1; private double coeffExtern = 1; private int gradientThr = 150; private int colorThr = 50; private int referenceThr = 50; private int backgroundModel = BACKGROUND_COLOR; private boolean drawColor = true; public static final int BACKGROUND_COLOR = 0; public static final int BACKGROUND_REFERENCE = 1; public static final int MERGE_CENTERS = 0; public static final int MERGE_EXTREMA = 1; public static final int MERGE_BOTH = 2; public static final int SPLIT_EXTERN = 1; public static final int SPLIT_INTERN = 2; public boolean DEBUG = false; public boolean DEBUG_LOCAL = true; public boolean DEBUG_GLOBAL = true; public boolean DEBUG_MINIMA = true; public int DEBUG_POINTS[]; public Snake(Point[] points) { this.points = points.clone(); size = points.length; deltaGradient = new boolean[size]; deltaBackground = new boolean[size]; Arrays.fill(deltaGradient, true); Arrays.fill(deltaBackground, true); } public Snake(int size) { this.size = size; points = new Point[size]; deltaGradient = new boolean[size]; deltaBackground = new boolean[size]; Arrays.fill(deltaGradient, true); Arrays.fill(deltaBackground, true); } public Snake(int size, int xmin, int xmax, int ymin, int ymax) { this(size); initialisation(xmin, xmax, ymin, ymax); } public Snake(int size, Point p1, Point p2) { this(size); initialisation(p1, p2); } public Snake(Snake s) { size = s.size; points = s.points.clone(); deltaGradient = s.deltaGradient.clone(); deltaBackground = s.deltaBackground.clone(); } public void clean() { gradient = null; reference = null; data = null; color = null; deltaGradient = null; deltaBackground = null; } public void updateSize() { deltaGradient = new boolean[size]; deltaBackground = new boolean[size]; Arrays.fill(deltaGradient, true); Arrays.fill(deltaBackground, true); } public void initialisation(int xmin, int xmax, int ymin, int ymax) { double xdelta, ydelta, delta; int k; if (size == 0) return; delta = size / 4.0; xdelta = (xmax - xmin) / delta; ydelta = (ymax - ymin) / delta; for (k = 0; k < delta; k++) points[k] = new Point(xmin + (int) (k * xdelta), ymin); for (k = 0; k < delta; k++) points[(int) (k + delta)] = new Point(xmax, ymin + (int) (k * ydelta)); for (k = 0; k < delta; k++) points[(int) (k + 2 * delta)] = new Point( xmax - (int) (k * xdelta), ymax); for (k = 0; k < delta; k++) points[(int) (k + 3 * delta)] = new Point(xmin, ymax - (int) (k * ydelta)); } public void initialisation(Point p1, Point p2) { Point min = minimum(p1, p2); Point max = maximum(p1, p2); initialisation(min.x, max.x, min.y, max.y); } public Point[] getPoints() { return points; } public int getSize() { return size; } public void setCoefficients(double cont, double ball, double curv, double grad, double background, double intern, double extern) { coeffContinuity = cont; coeffBalloon = ball; coeffCurvature = curv; coeffGradient = grad; coeffBackground = background; coeffIntern = intern; coeffExtern = extern; } public void setGradient(Image img) { gradient = img; } public void setReference(Image img) { reference = img; } public void setData(Image img) { data = img; computeColor(); } public void setZoom(int zoom) { this.zoom = zoom; } public void setNeighbourhood(int size) { sizeNeighbourhood = size; } public void setEpsilon(double eps) { epsilon = eps; } public void setGradientThreshold(int thr) { gradientThr = thr; } public void setReferenceThreshold(int thr) { referenceThr = thr; } public void setColorThreshold(int thr) { colorThr = thr; } public void setBackgroundModel(int model) { backgroundModel = model; } public boolean equals(Snake s) { if (s.size != size) return false; for (int i = 0; i < size; i++) if (!points[i].equals(s.points[i])) return false; return true; } public void invertPoint(int pos1, int pos2) { Point p = points[pos1]; points[pos1] = points[pos2]; points[pos2] = p; } public void insertPoint(int pos, Point p) { Point[] points2 = new Point[size + 1]; for (int i = 0; i < pos; i++) points2[i] = points[i]; points2[pos] = new Point(p); for (int i = pos; i < size; i++) points2[i + 1] = points[i]; size++; points = points2; updateSize(); } public void deletePoint(int pos) { if (size == 0) return; Point[] points2 = new Point[size - 1]; for (int i = 0; i < pos; i++) points2[i] = points[i]; for (int i = pos + 1; i < size; i++) points2[i - 1] = points[i]; size--; points = points2; updateSize(); } public void deleteDoubles(boolean successiveOnly) { if (size == 0) return; int s = size; if (successiveOnly) { // check successive only for (int k = 0; k < size - 1; k++) if (points[k].equals(points[k + 1])) { points[k] = null; s--; } if (points[0] != null) if (points[0].equals(points[size - 1])) { points[size - 1] = null; s--; } } else // check all doubles for (int k = 0; k < size; k++) for (int l = k + 1; l < size && points[k] != null; l++) if (points[k].equals(points[l])) { points[k] = null; s--; } // trim the result if (s != size) { Point tmp[] = new Point[s]; int l = 0; for (int k = 0; k < size; k++) if (points[k] != null) tmp[l++] = points[k]; points = tmp; size = s; updateSize(); } } public void deleteCrossings(boolean successiveOnly) { if (size == 0) return; int deleted[] = new int[2 * size]; int d = 0; if (successiveOnly) { // check quasi-successive only for (int k = 0; k < size - 1; k++) if (Line2D.linesIntersect(points[k].x, points[k].y, points[k + 1 > size - 1 ? 0 : k + 1].x, points[k + 1 > size - 1 ? 0 : k + 1].y, points[k + 2 > size - 1 ? (k + 2) % size : k + 2].x, points[k + 2 > size - 1 ? (k + 2) % size : k + 2].y, points[k + 3 > size - 1 ? (k + 3) % size : k + 3].x, points[k + 3 > size - 1 ? (k + 3) % size : k + 3].y)) { deleted[d++] = (k + 1) % size; // deleted[d++]=(k+2)%size; points[(k + 2) % size] = mean(points[(k + 1) % size], points[(k + 2) % size]); k++; } } else // check all crossings for (int k = 0; k < size; k++) for (int l = k + 2; l < size; l++) // Special case if (!(k == 0 && l == size - 1)) if (Line2D.linesIntersect(points[k].x, points[k].y, points[k + 1 > size - 1 ? 0 : k + 1].x, points[k + 1 > size - 1 ? 0 : k + 1].y, points[l].x, points[l].y, points[l + 1 > size - 1 ? 0 : l + 1].x, points[l + 1 > size - 1 ? 0 : l + 1].y)) { // Delete points between k and l if ((lengthCriteria == true && length(k, l) <= length( 0, k) + length(l, size - 1)) || (lengthCriteria == false && (l - k <= size / 2))) { // Add the midpoint between k+1 and l points[k + 1 > size - 1 ? 0 : k + 1] = mean( points[k + 1 > size - 1 ? 0 : k + 1], points[l]); for (int m = k + 2; m < l + 1; m++) deleted[d++] = m; } // Delete points between l and k else { for (int m = 0; m < k + 1; m++) deleted[d++] = m; // Add the midpoint between k and l+1 points[l + 1 > size - 1 ? 0 : l + 1] = mean( points[k], points[l + 1 > size - 1 ? 0 : l + 1]); for (int m = l + 2; m < size; m++) deleted[d++] = m; } } int s = d; /* * // to avoid an empty contour if (d>=size) return; */ for (int k = 0; k < d; k++) if (points[deleted[k]] != null) points[deleted[k]] = null; else s--; // trim the result if (s != 0) { Point tmp[] = new Point[size - s]; int l = 0; for (int k = 0; k < size; k++) if (points[k] != null) tmp[l++] = points[k]; points = tmp; size -= s; updateSize(); } } // TODO complete all case of this algorithm public void repareCrossings(boolean successiveOnly) { if (size == 0) return; if (successiveOnly) { // check quasi-successive only for (int k = 0; k < size - 1; k++) if (Line2D.linesIntersect(points[k].x, points[k].y, points[k + 1 > size - 1 ? 0 : k + 1].x, points[k + 1 > size - 1 ? 0 : k + 1].y, points[k + 2 > size - 1 ? (k + 2) % size : k + 2].x, points[k + 2 > size - 1 ? (k + 2) % size : k + 2].y, points[k + 3 > size - 1 ? (k + 3) % size : k + 3].x, points[k + 3 > size - 1 ? (k + 3) % size : k + 3].y)) { invertPoint(k + 1, k + 2); k++; } } else // check all crossings for (int k = 0; k < size; k++) for (int l = k + 2; l < size; l++) // Special case if (!(k == 0 && l == size - 1)) if (Line2D.linesIntersect(points[k].x, points[k].y, points[k + 1 > size - 1 ? 0 : k + 1].x, points[k + 1 > size - 1 ? 0 : k + 1].y, points[l].x, points[l].y, points[l + 1 > size - 1 ? 0 : l + 1].x, points[l + 1 > size - 1 ? 0 : l + 1].y)) { // Invert points between k and l if ((lengthCriteria == true && length(k, l) <= length( 0, k) + length(l, size - 1)) || (lengthCriteria == false && (l - k <= size / 2))) for (int m = k + 1; m < l + 1; m++) invertPoint(m, size - m); // Invert points between l and k // TODO implement this feature } } public static Snake add(Snake s1, Snake s2) { if (s1 == null) return s2; else return s1.add(s2); } public Snake add(Snake s) { if (s == null) return this; Snake res = new Snake(size + s.size); for (int k = 0; k < size; k++) res.points[k] = new Point(points[k]); for (int k = 0; k < s.size; k++) res.points[k + size] = new Point(s.points[k]); return res; } public Snake extract(int pos1, int pos2) { if (pos2 - pos1 < 0) return null; Snake res = new Snake(1 + pos2 - pos1); for (int k = 0; k < 1 + pos2 - pos1; k++) res.points[k] = new Point(points[pos1 + k]); return res; } public double length(int pos1, int pos2) { double dist = 0; for (int k = pos1; k < pos2; k++) dist += points[k].distance(points[k + 1]); return dist; } public double computeAverageDistance() { double dist = length(0, size - 1); dist += points[size - 1].distance(points[0]); dist /= size; return dist; } public void computeColor() { color = new int[data.getBDim()]; for (int b = 0; b < data.getBDim(); b++) { color[b] = 0; if (colorContour) { for (int t = 0; t < data.getTDim(); t++) for (int z = 0; z < data.getZDim(); z++) for (int k = 0; k < size; k++) color[b] += data.getPixelByte(points[k].x - shifted.x, points[k].y - shifted.y, z, t, b); color[b] /= size; } else { for (int t = 0; t < data.getTDim(); t++) for (int z = 0; z < data.getZDim(); z++) for (int x = 0; x < data.getXDim(); x++) for (int y = 0; y < data.getYDim(); y++) color[b] += data.getPixelByte(x, y, z, t, b); color[b] /= data.getTDim() * data.getZDim() * data.getYDim() * data.getXDim(); } } } public void computeShift() { shifted = getMin(); shifted.translate(-zoom, -zoom); } public Point getCenter() { int x = 0; int y = 0; for (int k = 0; k < size; k++) { x += points[k].x; y += points[k].y; } Point res = new Point(x / size, y / size); return res; } public Point getMin() { int x = Integer.MAX_VALUE; int y = Integer.MAX_VALUE; for (int k = 0; k < size; k++) { if (points[k].x < x) x = points[k].x; if (points[k].y < y) y = points[k].y; } Point res = new Point(x, y); return res; } public Point getMax() { int x = Integer.MIN_VALUE; int y = Integer.MIN_VALUE; for (int k = 0; k < size; k++) { if (points[k].x > x) x = points[k].x; if (points[k].y > y) y = points[k].y; } Point res = new Point(x, y); return res; } public Snake merge(Snake snake) { if (snake == null) return this; // Compute extrema Point max1 = this.getMax(); Point max2 = snake.getMax(); Point min1 = this.getMin(); Point min2 = snake.getMin(); Point min = minimum(min1, min2); Point max = maximum(max1, max2); // Create new snake return new Snake(size + snake.size, min, max); } public Snake[] split(int mode) { int m = 0; int invalid[] = new int[size]; Snake[] res; if (mode == SPLIT_EXTERN) // Check which point has to be deleted for (int k = 0; k < size; k++) if (deltaGradient[k] == false && deltaBackground[k] == false) invalid[m++] = k; // Special case if no split is required if (m < 1) { res = new Snake[1]; res[0] = this; return res; } // Split the snake by keeping valid successive points sequences int l = 0; res = new Snake[m + 1]; for (int k = 0; k < m - 1; k++) { // compute the end of the invalid points local sequence int k2 = k; while (k2 < m - 1 && invalid[k2 + 1] - invalid[k2] == 1) k2++; if (k2 < m - 1) res[l++] = extract(invalid[k2] + 1, invalid[k2 + 1] - 1); k = k2; } // Special processing for invalid[m-1],invalid[0] Snake s, s1 = null, s2 = null; if (invalid[m - 1] + 1 < size) s1 = extract(invalid[m - 1], size - 1); if (invalid[0] > 0) s2 = extract(0, invalid[0]); s = Snake.add(s1, s2); if (s != null) res[l++] = s; return trim(res); } public boolean isValid(int sizeMin, int widthMin, int heightMin, int areaMin) { Point max = getMax(); Point min = getMin(); Point diff = diff(min, max); if (size < sizeMin) return false; if (diff.x < widthMin || diff.y < heightMin) return false; if (diff.x * diff.y < areaMin) return false; return true; } public Image drawOnImage(Image input, boolean point, boolean segment) { Image output = input.copyImage(true); if (segment) { Point[] tmp; for (int k = 0; k < size; k++) { tmp = bresenham(points[k], points[k + 1 > size - 1 ? 0 : k + 1]); for (int l = 0; l < tmp.length; l++) if (tmp[l].x >= 0 && tmp[l].x < input.getXDim() && tmp[l].y >= 0 && tmp[l].y < input.getYDim()) for (int z = 0; z < input.getZDim(); z++) for (int t = 0; t < input.getTDim(); t++) for (int b = 0; b < input.getBDim(); b++) output.setPixelBoolean(tmp[l].x, tmp[l].y, z, t, b, drawColor); } } if (point) { for (int k = 0; k < size; k++) if (points[k].x >= 0 && points[k].x < input.getXDim() && points[k].y >= 0 && points[k].y < input.getYDim()) for (int z = 0; z < input.getZDim(); z++) for (int t = 0; t < input.getTDim(); t++) for (int b = 0; b < input.getBDim(); b++) { for (int lx = -1; lx < 2; lx++) for (int ly = -1; ly < 2; ly++) if (points[k].x + lx >= 0 && points[k].y + ly >= 0 && points[k].x + lx < input .getXDim() && points[k].y + ly < input .getYDim()) output.setPixelBoolean(points[k].x + lx, points[k].y + ly, z, t, b, !drawColor); output.setPixelBoolean(points[k].x, points[k].y, z, t, b, drawColor); } } return output; } void computeEnergyContinuity(Neighbourhood n, int pos) { Point tmp = new Point(); int dx, dy; int shift = (n.size - 1) / 2; for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { tmp.setLocation(points[pos].x + dx - shift, points[pos].y + dy - shift); n.values[dx][dy] = Math.abs(averageDistance - tmp .distance(points[pos - 1 < 0 ? size - 1 : pos - 1])); } if (normaliseNeighbourhood) n.normalisation(); if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "CT:" + n); } void computeEnergyBalloon(Neighbourhood n, int pos) { int dx, dy; int shift = (n.size - 1) / 2; double prev, next, nix, niy; // Norm computing using previous and next points prev = points[pos].distance(points[pos - 1 < 0 ? size - 1 : pos - 1]); next = points[pos].distance(points[pos + 1 > size - 1 ? 0 : pos + 1]); if (prev == 0 || next == 0) { n.fill(0); return; } // Normal vector computation and update normal vector nix = (points[pos].x - points[pos - 1 < 0 ? size - 1 : pos - 1].x) / prev + (points[pos].x - points[pos + 1 > size - 1 ? 0 : pos + 1].x) / next; if (nix == 0) nix = normalx; else normalx = nix; niy = (points[pos].y - points[pos - 1 < 0 ? size - 1 : pos - 1].y) / prev + (points[pos].y - points[pos + 1 > size - 1 ? 0 : pos + 1].y) / next; if (niy == 0) niy = normaly; else normaly = niy; // Energy computation for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) n.values[dx][dy] = (dx - shift) * nix + (dy - shift) * niy; if (normaliseNeighbourhood) n.normalisation(); if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "BA:" + n); } void computeEnergyCurvature(Neighbourhood n, int pos) { Point tmp = new Point(); int dx, dy; double dist; int shift = (n.size - 1) / 2; double prev, next; // Distance prev-next computing dist = points[pos - 1 < 0 ? size - 1 : pos - 1] .distance(points[pos + 1 > size - 1 ? 0 : pos + 1]); if (dist == 0) { n.fill(0); return; } // Energy computation for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { tmp.setLocation(points[pos].x + dx - shift, points[pos].y + dy - shift); prev = tmp.distance(points[pos - 1 < 0 ? size - 1 : pos - 1]); next = tmp.distance(points[pos + 1 > size - 1 ? 0 : pos + 1]); n.values[dx][dy] = (prev + next) / dist - 1; // why -1 ? } if (normaliseNeighbourhood) n.normalisation(); if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "CV:" + n); } void computeEnergyGradient(Neighbourhood n, int pos) { int dx, dy; double val; int shift = (n.size - 1) / 2; for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { val = 0; for (int b = 0; b < gradient.getBDim(); b++) val += gradient.getPixelByte((int) (points[pos].x + dx - shift - shifted.x), (int) (points[pos].y + dy - shift - shifted.y), 0, 0, 0); if (val < gradientThr * gradient.getBDim()) n.values[dx][dy] = 0; else n.values[dx][dy] = -val; } if (normaliseNeighbourhood) n.normalisation(); deltaGradient[pos] = !n.constant; if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "GR:" + n); } void computeEnergyReference(Neighbourhood n, int pos) { int dx, dy; double val; int shift = (n.size - 1) / 2; for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { val = 0; for (int b = 0; b < reference.getBDim(); b++) val += reference.getPixelByte((int) (points[pos].x + dx - shift - shifted.x), (int) (points[pos].y + dy - shift - shifted.y), 0, 0, 0); if (val < referenceThr * reference.getBDim()) n.values[dx][dy] = 0; else n.values[dx][dy] = -val; } if (normaliseNeighbourhood) n.normalisation(); deltaBackground[pos] = !n.constant; if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "BG:" + n); } void computeEnergyColor(Neighbourhood n, int pos) { int dx, dy; int diff; int shift = (n.size - 1) / 2; for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { diff = 0; for (int b = 0; b < data.getBDim(); b++) diff += Math.abs(data.getPixelByte((int) (points[pos].x + dx - shift - shifted.x), (int) (points[pos].y + dy - shift - shifted.y), 0, 0, b) - color[b]); if (diff < colorThr * data.getBDim()) n.values[dx][dy] = 0; else n.values[dx][dy] = diff; } if (normaliseNeighbourhood) n.normalisation(); deltaBackground[pos] = !n.constant; if (DEBUG && DEBUG_LOCAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, pos) >= 0) System.out.println(pos + ":" + points[pos].x + "," + points[pos].y + ":" + "CL:" + n); } void computeEnergyGreen(Neighbourhood n, int pos) { if (data.getBDim() < 3) return; int dx, dy; int ndg; double green; int shift = (n.size - 1) / 2; for (dx = 0; dx < n.size; dx++) for (dy = 0; dy < n.size; dy++) { ndg = 0; for (int b = 0; b < data.getBDim(); b++) ndg += data.getPixelByte( (int) (points[pos].x + dx - shift - shifted.x), (int) (points[pos].y + dy - shift - shifted.y), 0, 0, b); green = data .getPixelByte( (int) (points[pos].x + dx - shift - shifted.x), (int) (points[pos].y + dy - shift - shifted.y), 0, 0, 1); if (green / ndg < epsilon) n.values[dx][dy] = 0; else n.values[dx][dy] = 1.0 - green / ndg; } } public boolean deform() { if (size == 0) return false; Neighbourhood intern = new Neighbourhood(sizeNeighbourhood); Neighbourhood extern = new Neighbourhood(sizeNeighbourhood); Neighbourhood local = new Neighbourhood(sizeNeighbourhood); Snake prev = new Snake(this); Point[] moves = new Point[size]; // Parameters initialisation averageDistance = computeAverageDistance(); if (partialImage) computeShift(); normalx = normaly = 0; for (int k = 0; k < size; k++) { // Computation of intern energy intern.fill(0); if (coeffContinuity != 0 && coeffIntern != 0) { computeEnergyContinuity(local, k); intern.add(local, coeffContinuity); } if (coeffBalloon != 0 && coeffIntern != 0) { computeEnergyBalloon(local, k); intern.add(local, coeffBalloon); } if (coeffCurvature != 0 && coeffIntern != 0) { computeEnergyCurvature(local, k); intern.add(local, coeffCurvature); } intern.divide(coeffBalloon + coeffBalloon + coeffCurvature); // intern.normalisation(); if (DEBUG && DEBUG_GLOBAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, k) >= 0) System.out.println(k + ":" + points[k].x + "," + points[k].y + ":" + "I:" + intern); // Computation of extern energy extern.fill(0); if (coeffGradient != 0 && coeffExtern != 0) { computeEnergyGradient(local, k); extern.add(local, coeffGradient); } if (coeffBackground != 0 && coeffExtern != 0) { if (backgroundModel == BACKGROUND_COLOR) computeEnergyColor(local, k); if (backgroundModel == BACKGROUND_REFERENCE) computeEnergyReference(local, k); extern.add(local, coeffBackground); } extern.divide(coeffGradient + coeffBackground); // extern.normalisation(); if (DEBUG && DEBUG_GLOBAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, k) >= 0) System.out.println(k + ":" + points[k].x + "," + points[k].y + ":" + "E:" + extern); // Computation of global energy local.fill(0); local.add(intern, coeffIntern); local.add(extern, coeffExtern); if (DEBUG && DEBUG_GLOBAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, k) >= 0) System.out.println(k + ":" + points[k].x + "," + points[k].y + ":" + "T:" + local); // Look for the minimum moves[k] = local.minimum(); if (DEBUG && DEBUG_GLOBAL) if (DEBUG_POINTS == null || Arrays.binarySearch(DEBUG_POINTS, k) >= 0) System.out .println(k + ":" + points[k].x + "," + points[k].y + ":" + moves[k].x + "," + moves[k].y); } // Perform translation of all points for (int k = 0; k < size; k++) points[k].translate(moves[k].x, moves[k].y); // Optionnal : avoid oscillations for (int k = 0; k < size - 1; k++) if (points[k].equals(prev.points[k + 1]) && points[k + 1].equals(prev.points[k])) invertPoint(k, k + 1); if (points[0].equals(prev.points[size - 1]) && points[0].equals(prev.points[size - 1])) invertPoint(0, size - 1); // Check convergence return (!equals(prev)); } public void crop(Point p1, Point p2) { Point min = minimum(p1, p2); Point max = maximum(p1, p2); for (int k = 0; k < size; k++) { if (points[k].x > max.x) points[k].x = max.x; if (points[k].y > max.y) points[k].y = max.y; if (points[k].x < min.x) points[k].x = min.x; if (points[k].y < min.y) points[k].y = min.y; } } public static Snake[] merge(Snake[] snakes, double param, int mode) { // Special case if less than 2 snakes to merge if (snakes.length < 2) return snakes; Point[] centers = null; Point[] min = null; Point[] max = null; Point[] diff = null; // Compute barycenters if (mode == MERGE_CENTERS || mode == MERGE_BOTH) { centers = new Point[snakes.length]; for (int k = 0; k < snakes.length; k++) centers[k] = snakes[k].getCenter(); } // Compute extrema if (mode == MERGE_EXTREMA || mode == MERGE_BOTH) { min = new Point[snakes.length]; max = new Point[snakes.length]; diff = new Point[snakes.length]; for (int k = 0; k < snakes.length; k++) { min[k] = snakes[k].getMin(); max[k] = snakes[k].getMax(); diff[k] = diff(min[k], max[k]); } } // Compare barycenters int merged[]; int m; // Process each snake for (int k = 0; k < snakes.length; k++) if (snakes[k] != null) { merged = new int[snakes.length]; m = 0; // Check for close barycenters for (int l = k + 1; l < snakes.length; l++) { if (mode == MERGE_CENTERS || mode == MERGE_BOTH) if (centers[k].distance(centers[l]) < param) merged[m++] = l; if (mode == MERGE_EXTREMA || mode == MERGE_BOTH) if (diff(max[k], min[l]).x > diff[k].x * param && diff(max[k], min[l]).y > diff[k].y * param) merged[m++] = l; } // Merge snakes iteratively for (int n = 0; n < m; n++) { snakes[k] = snakes[k].merge(snakes[merged[n]]); snakes[merged[n]] = null; } } // Create result return trim(snakes); } public static Snake[] split(Snake[] snakes, int mode) { ArrayList<Snake> v = new ArrayList<Snake>(); if (snakes == null) return snakes; // split each snake of the array and add the results to the vector for (int k = 0; k < snakes.length; k++) v.addAll(Arrays.asList(snakes[k].split(mode))); return v.toArray(new Snake[0]); } public static Snake[] filter(Snake[] snakes, int sizeMin, int widthMin, int heightMin, int areaMin) { for (int k = 0; k < snakes.length; k++) if (!snakes[k].isValid(sizeMin, widthMin, heightMin, areaMin)) snakes[k] = null; return trim(snakes); } public static Image draw(Snake[] snakes, Image inputImage, boolean point, boolean segment) { Image tmp = inputImage.copyImage(true); for (int k = 0; k < snakes.length; k++) tmp = snakes[k].drawOnImage(tmp, point, segment); return tmp; } public static void clean(Snake[] snakes) { for (int k = 0; k < snakes.length; k++) snakes[k].clean(); } public static Snake[] trim(Snake[] snakes) { int size = 0; for (int k = 0; k < snakes.length; k++) if (snakes[k] != null) size++; if (size == snakes.length) return snakes; Snake tmp[] = new Snake[size]; int l = 0; for (int k = 0; k < snakes.length; k++) if (snakes[k] != null) tmp[l++] = snakes[k]; return tmp; } static Point minimum(Point p1, Point p2) { int x = Math.min(p1.x, p2.x); int y = Math.min(p1.y, p2.y); return new Point(x, y); } static Point maximum(Point p1, Point p2) { int x = Math.max(p1.x, p2.x); int y = Math.max(p1.y, p2.y); return new Point(x, y); } static Point mean(Point p1, Point p2) { int x = p1.x + p2.x; int y = p1.y + p2.y; return new Point(x / 2, y / 2); } static Point diff(Point p1, Point p2) { int x = Math.abs(p1.x - p2.x); int y = Math.abs(p1.y - p2.y); return new Point(x, y); } /** * Algorithme de trace de segments de Bresenham Code inspire de celui * propose par Karl Tombre (LORIA Nancy) * * @param p1 * premier point * @param p2 * second point * @return liste de points a tracer */ static Point[] bresenham(Point p1, Point p2) { ArrayList<Point> v = new ArrayList<Point>(); int deltaX = Math.abs(p2.x - p1.x); int deltaY = Math.abs(p2.y - p1.y); Point p; Point pFinal; if (deltaX > deltaY) { // direction principale Ox if (p1.getX() < p2.getX()) { // calcul des points de depart et d'arrivee p = new Point(p1); pFinal = new Point(p2); } else { p = new Point(p2); pFinal = new Point(p1); } int e = 2 * deltaY - deltaX; // Calcul des increments de e int horiz = 2 * deltaY; int diago = 2 * (deltaY - deltaX); // Booleen pour marquer si on est en y croissant ou non boolean croissant = p.getY() < pFinal.getY(); // y croissant ou // non for (int i = 0; i < deltaX; i++) { v.add(new Point(p)); if (e > 0) { if (croissant) p.translate(0, 1); else p.translate(0, -1); e += diago; } else e += horiz; // Dans tous les cas p.translate(1, 0); } } else { // direction principale Oy if (p1.getY() < p2.getY()) { p = new Point(p1); pFinal = new Point(p2); } else { p = new Point(p2); pFinal = new Point(p1); } int e = 2 * deltaX - deltaY; // Calcul des increments de e int verti = 2 * deltaX; int diago = 2 * (deltaX - deltaY); boolean croissant = p.getX() < pFinal.getX(); // x croissant ou // non for (int i = 0; i < deltaY; i++) { v.add(new Point(p)); if (e > 0) { if (croissant) p.translate(1, 0); else p.translate(-1, 0); e += diago; } else e += verti; // Dans tous les cas p.translate(0, 1); } } // Tracer le dernier point... v.add(new Point(pFinal)); return v.toArray(new Point[0]); } class Neighbourhood { int size; double[][] values; boolean constant; Neighbourhood(int size) { this.size = size; values = new double[size][size]; } Neighbourhood(int size, double val) { this(size); fill(val); } void fill(double val) { for (int dx = 0; dx < size; dx++) for (int dy = 0; dy < size; dy++) values[dx][dy] = val; } void divide(double val) { if (val != 0) for (int dx = 0; dx < size; dx++) for (int dy = 0; dy < size; dy++) values[dx][dy] /= val; } void normalisation() { double min, max, diff; int dx, dy; // Extrema computation min = Double.MAX_VALUE; max = -Double.MAX_VALUE; for (dx = 0; dx < size; dx++) for (dy = 0; dy < size; dy++) { if (values[dx][dy] < min) min = values[dx][dy]; if (values[dx][dy] > max) max = values[dx][dy]; } diff = max - min; if (diff == 0) { diff = 1; constant = true; } else constant = false; // Normalisation for (dx = 0; dx < size; dx++) for (dy = 0; dy < size; dy++) values[dx][dy] = (values[dx][dy] - min) / diff; } void add(Neighbourhood n, double coeff) { if (n.size != size) return; for (int dx = 0; dx < size; dx++) for (int dy = 0; dy < size; dy++) values[dx][dy] += n.values[dx][dy] * coeff; } Point minimum() { double min = values[size / 2][size / 2] - epsilon; int xmin = size / 2, ymin = size / 2; int nbmin = 1; for (int dx = 0; dx < size; dx++) for (int dy = 0; dy < size; dy++) if (values[dx][dy] < min + epsilon) { nbmin = 1; xmin = dx; ymin = dy; min = values[dx][dy]; } // check if several minima else if (values[dx][dy] == min) nbmin++; // Several minima, not the neighbourhood center, random choice if (nbmin > 1) { if (xmin != size / 2 && ymin != size / 2) { int selected = (int) (Math.random() * nbmin); if (DEBUG && DEBUG_MINIMA) System.out.println(selected + "/" + nbmin); int k = 0; for (int dx = 0; dx < size; dx++) for (int dy = 0; dy < size; dy++) if (values[dx][dy] == min) { if (k == selected) { xmin = dx; ymin = dy; } k++; } } else if (DEBUG && DEBUG_MINIMA) System.out.println("_" + "/" + nbmin); } int shift = (size - 1) / 2; return new Point(xmin - shift, ymin - shift); } public String toString() { StringBuffer str = new StringBuffer(); for (int dx = 0; dx < size; dx++) { for (int dy = 0; dy < size; dy++) str.append((int) (values[dx][dy] * 100)).append(" "); str.append(" | "); } return str.toString(); } } }