/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.ui.model.utils.algo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
import javax.media.jai.PlanarImage;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;
import org.weasis.core.ui.model.utils.GraphicUtil;
/**
* The Class BlobAnalyse2D.
*
* @author Nicolas Roduit
*/
public class BlobAnalyse2D {
private PlanarImage source;
// méthode d'itération qui permet de connaître la postion des pixels
private RandomIter src;
Rectangle imgbound;
private final Point firstPoint;
private boolean[][] visited;
private final int dwidth;
private final int dheight;
private ArrayList<Point> blob = null;
private Shape shape;
private int area;
public BlobAnalyse2D(Shape shape) {
this.shape = shape;
this.area = 0;
this.source = GraphicUtil.getGraphicAsImage(shape);
this.imgbound = source.getBounds();
this.src = RandomIterFactory.create(source, null);
this.dwidth = source.getWidth();
this.dheight = source.getHeight();
this.firstPoint = new Point();
iniToVisited(src, 0);
}
public BlobAnalyse2D(PlanarImage binary) {
this.area = 0;
this.source = binary;
this.imgbound = binary.getBounds();
this.src = RandomIterFactory.create(binary, null);
this.dwidth = binary.getWidth();
this.dheight = binary.getHeight();
this.firstPoint = new Point();
}
private void iniToVisited(RandomIter data, int val) {
int[] pix = { 0 };
visited = new boolean[dheight][dwidth];
// met à true les pixels du background
for (int j = 0; j < dheight; j++) {
for (int i = 0; i < dwidth; i++) {
data.getPixel(i + imgbound.x, j + imgbound.y, pix);
if (pix[0] == val) {
visited[j][i] = true;
}
}
}
}
// public ClassGraphic transformToClassGraphic(ClassData classData) {
// for (int j = 0; j < dheight; j++) {
// for (int i = 0; i < dwidth; i++) {
// if (visited[j][i] == false) {
// int area = getArea();
// Contour contour = new Contour(shape.getBounds().x + i, shape.getBounds().y + j, chain8(i, j), area,
// getStatValue(blobSumCoorXY));
// ClassGraphic object = new ClassGraphic(classData, contour);
// return object;
// }
// }
// }
// return null;
// }
public Contour getContour() {
for (int j = 0; j < dheight; j++) {
for (int i = 0; i < dwidth; i++) {
if (visited[j][i] == false) {
int area = getArea();
Contour contour = new Contour(shape.getBounds().x + i, shape.getBounds().y + j, chain8(i, j), area,
getStatValue(blob));
return contour;
}
}
}
return null;
}
public Vector<Contour> getHolesContour() {
Vector<Contour> holes = new Vector<>();
iniToVisited(src, 0);
int[] area = { 0 };
for (int m = 0; m < dheight; m++) {
for (int n = 0; n < dwidth; n++) {
if (!visited[m][n]) {
if (!growingBHoleSize(n, m, area)) {
byte[] holeChain = chainHole8(n, m - 1);
Contour contour = new Contour(shape.getBounds().x + +n, shape.getBounds().y + +m - 1, holeChain,
area[0], null);
holes.add(contour);
}
}
}
}
holes.trimToSize();
holes = holes.isEmpty() ? null : holes;
return holes;
}
private byte[] chainHole8(int x, int y) {
/* Table given index offset for each of the 8 directions. */
int[] dx = { 1, 1, 0, -1, -1, -1, 0, 1 };
int[] dy = { 0, -1, -1, -1, 0, 1, 1, 1 };
Byte direction = null;
boolean foundPix;
byte dirTemp;
int row = y;
int col = x;
int lastdir = 6; // dernière direction
List<Byte> chain = new ArrayList<>();
int[] pix = { 0 };
src.getPixel(x, y, pix);
// initalise val avec la valeur d'intensité du blob
int val = pix[0];
do {
foundPix = false;
for (int i = lastdir + 1; i < lastdir + 8; i++) { /* Look for next */
dirTemp = (byte) (i % 8); // reste de la division par 8
int xp4 = dx[dirTemp] + col;
int yp4 = dy[dirTemp] + row;
// teste si la nouvelle position est bien dans l'image
if (xp4 >= 0 && xp4 < dwidth && yp4 >= 0 && yp4 < dheight) {
src.getPixel(xp4, yp4, pix);
if (pix[0] == val) { // si la nouvelle position à un valeur d'intensité identique
direction = dirTemp;
foundPix = true;
break;
}
}
}
if (foundPix) { /* Found a next pixel ... */
chain.add(direction); /* Save direction as code */
row += dy[direction.intValue()];
col += dx[direction.intValue()];
lastdir = (direction + 5) % 8;
} else {
break;
}
} while ((row != y) || (col != x)); /* Stop when next to start pixel */
byte[] tab2;
tab2 = new byte[chain.size()];
for (int i = 0; i < tab2.length; i++) {
tab2[i] = chain.get(i);
}
return tab2;
}
private boolean growingBHoleSize(int i, int j, int[] area) {
Stack<Integer[]> stack = new Stack<>();
area[0] = 1;
boolean borderHit = false;
// teste si le point de départ touche le bord
if (i == 0 || i == dwidth - 1 || j == 0 || j == dheight - 1) {
borderHit = true;
}
visited[j][i] = true;
stack.push(new Integer[] { i, j });
while (!stack.empty()) {
Integer ai[] = stack.pop();
int x = ai[0];
int y = ai[1];
int xp1 = x;
int yp1 = y - 1;
if (xp1 >= 0 && xp1 < dwidth && yp1 >= 0 && yp1 < dheight) {
if (visited[yp1][xp1] == false) {
if (xp1 == 0 || xp1 == dwidth - 1 || yp1 == 0 || yp1 == dheight - 1) {
borderHit = true;
} else {
area[0]++;
}
visited[yp1][xp1] = true;
stack.push(new Integer[] { xp1, yp1 });
}
}
int xp2 = x;
int yp2 = y + 1;
if (xp2 >= 0 && xp2 < dwidth && yp2 >= 0 && yp2 < dheight) {
if (visited[yp2][xp2] == false) {
if (xp2 == 0 || xp2 == dwidth - 1 || yp2 == 0 || yp2 == dheight - 1) {
borderHit = true;
} else {
area[0]++;
}
visited[yp2][xp2] = true;
stack.push(new Integer[] { xp2, yp2 });
}
}
int xp3 = x + 1;
int yp3 = y;
if (xp3 >= 0 && xp3 < dwidth && yp3 >= 0 && yp3 < dheight) {
if (visited[yp3][xp3] == false) {
if (xp3 == 0 || xp3 == dwidth - 1 || yp3 == 0 || yp3 == dheight - 1) {
borderHit = true;
} else {
area[0]++;
}
visited[yp3][xp3] = true;
stack.push(new Integer[] { xp3, yp3 });
}
}
int xp4 = x - 1;
int yp4 = y;
if (xp4 >= 0 && xp4 < dwidth && yp4 >= 0 && yp4 < dheight) {
if (visited[yp4][xp4] == false) {
if (xp4 == 0 || xp4 == dwidth - 1 || yp4 == 0 || yp4 == dheight - 1) {
borderHit = true;
} else {
area[0]++;
}
visited[yp4][xp4] = true;
stack.push(new Integer[] { xp4, yp4 });
}
}
}
return borderHit;
}
public int getArea() {
// renvoie l'aire de l'image de ROIShape
if (blob == null) {
for (int j = 0; j < dheight; j++) {
for (int i = 0; i < dwidth; i++) {
if (!visited[j][i]) {
firstPoint.x = i;
firstPoint.y = j;
return area = growingSize(i, j);
}
}
}
}
return area;
}
public double getPerimeter() {
getArea();
return computePerimeter(chain8(firstPoint.x, firstPoint.y));
}
public static double computePerimeter(byte[] chain) {
int corner = 0;
int evenCode = 0;
int oddCode = 0;
// si le blob mesure 1 pixel, il y a que le point de départ et la chaine est nulle
if (chain.length == 0) {
return 2d;
}
// process le 1er élément de la chaine
if ((chain[0] % 2) != 0) {
oddCode++;
} else {
evenCode++;
}
for (int i = 1; i < chain.length; i++) {
int code = chain[i];
// si il y a un reste à la division par 2, alors incrémente le compteur impair, sinon pair
if (code % 2 != 0) {
oddCode++;
} else {
evenCode++;
// le compteur corner comptabilise le nombre de changement de direction de la chaîne
}
if (code != chain[i - 1]) {
corner++;
}
}
// Vossepoel & Smeulders (1982) :
// Ne = Number of even chain codes
// No = Number of odd chain codes
// Nc = Number of "corners" (where the chain code changes)
// Perimeter = (0.980) Ne + (1.406) No - (0.091) Nc
return (0.98d * evenCode) + (1.406d * oddCode) - (0.091d * corner);
}
public List<Point> getBlobSumCoorXY() {
getArea();
return blob;
}
private byte[] chain8(int x, int y) {
x += imgbound.x;
y += imgbound.y;
// Table given index offset for each of the 8 directions.
int[] dx = { 1, 1, 0, -1, -1, -1, 0, 1 };
int[] dy = { 0, -1, -1, -1, 0, 1, 1, 1 };
Byte direction = null;
boolean foundPix;
int dirTemp;
int row = y;
int col = x;
int lastdir = 4; // dernière direction
List<Byte> chain = new ArrayList<>();
// intialise les valeurs de la boundingBox
int maxX = imgbound.x + imgbound.width;
int maxY = imgbound.y + imgbound.height;
int[] pix = { 0 };
src.getPixel(x, y, pix);
// initalise val avec la valeur d'intensité du blob
int val = pix[0];
do {
foundPix = false;
for (int i = lastdir + 1; i < lastdir + 8; i++) { /* Look for next */
dirTemp = i % 8; // reste de la division par 8
int xp4 = dx[dirTemp] + col;
int yp4 = dy[dirTemp] + row;
// teste si la nouvelle position est bien dans l'image
if (xp4 >= imgbound.x && xp4 < maxX && yp4 >= imgbound.y && yp4 < maxY) {
src.getPixel(xp4, yp4, pix);
if (pix[0] == val) { // si la nouvelle position à un valeur d'intensité identique
// attribution d'un code de direction de Freeman en connectivité 8
// code de Freeman : où 0 est à l'ouest, 1 au nord-ouest, 2 au nord, 3 au nord-est 4 à l'est ...
direction = (byte) dirTemp;
foundPix = true;
break;
}
}
}
if (foundPix) { /* Found a next pixel ... */
chain.add(direction); /* Save direction as code */
row += dy[direction.intValue()];
col += dx[direction.intValue()];
lastdir = (direction + 5) % 8;
} else {
break; /* NO next pixel, la chaine n'est pas fermée */
}
} while ((row != y) || (col != x)); /* Stop when next to start pixel */
byte[] tab = new byte[chain.size()];
for (int i = 0; i < tab.length; i++) {
tab[i] = chain.get(i);
}
return tab;
}
int growingSize(int i, int j) {
blob = new ArrayList<>();
Stack<Point> stack = new Stack<>();
int size = 1;
Point startp = new Point(i, j);
blob.add(startp);
visited[j][i] = true;
stack.push(startp);
while (!stack.empty()) {
Point lp = stack.pop();
int xp1 = lp.x;
int yp1 = lp.y - 1;
if (xp1 >= 0 && xp1 < dwidth && yp1 >= 0 && yp1 < dheight) {
if (visited[yp1][xp1] == false) {
Point p = new Point(xp1, yp1);
blob.add(p);
size++;
visited[yp1][xp1] = true;
stack.push(p);
}
}
int xp2 = lp.x;
int yp2 = lp.y + 1;
if (xp2 >= 0 && xp2 < dwidth && yp2 >= 0 && yp2 < dheight) {
if (visited[yp2][xp2] == false) {
Point p = new Point(xp2, yp2);
blob.add(p);
size++;
visited[yp2][xp2] = true;
stack.push(p);
}
}
int xp3 = lp.x + 1;
int yp3 = lp.y;
if (xp3 >= 0 && xp3 < dwidth && yp3 >= 0 && yp3 < dheight) {
if (visited[yp3][xp3] == false) {
Point p = new Point(xp3, yp3);
blob.add(p);
size++;
visited[yp3][xp3] = true;
stack.push(p);
}
}
int xp4 = lp.x - 1;
int yp4 = lp.y;
if (xp4 >= 0 && xp4 < dwidth && yp4 >= 0 && yp4 < dheight) {
if (visited[yp4][xp4] == false) {
Point p = new Point(xp4, yp4);
blob.add(p);
size++;
visited[yp4][xp4] = true;
stack.push(p);
}
}
int xp5 = lp.x + 1;
int yp5 = lp.y - 1;
if (xp5 >= 0 && xp5 < dwidth && yp5 >= 0 && yp5 < dheight) {
if (visited[yp5][xp5] == false) {
Point p = new Point(xp5, yp5);
blob.add(p);
size++;
visited[yp5][xp5] = true;
stack.push(p);
}
}
int xp6 = lp.x - 1;
int yp6 = lp.y + 1;
if (xp6 >= 0 && xp6 < dwidth && yp6 >= 0 && yp6 < dheight) {
if (visited[yp6][xp6] == false) {
Point p = new Point(xp6, yp6);
blob.add(p);
size++;
visited[yp6][xp6] = true;
stack.push(p);
}
}
int xp7 = lp.x + 1;
int yp7 = lp.y + 1;
if (xp7 >= 0 && xp7 < dwidth && yp7 >= 0 && yp7 < dheight) {
if (visited[yp7][xp7] == false) {
Point p = new Point(xp7, yp7);
blob.add(p);
size++;
visited[yp7][xp7] = true;
stack.push(p);
}
}
int xp8 = lp.x - 1;
int yp8 = lp.y - 1;
if (xp8 >= 0 && xp8 < dwidth && yp8 >= 0 && yp8 < dheight) {
if (visited[yp8][xp8] == false) {
Point p = new Point(xp8, yp8);
blob.add(p);
size++;
visited[yp8][xp8] = true;
stack.push(p);
}
}
}
return size;
}
public static List<Double> getStatValue(List<Point> blobXY) {
ArrayList<Double> list = new ArrayList<>(11);
int sumX = 0;
int sumY = 0;
for (Point p : blobXY) {
sumX += p.x;
sumY += p.y;
}
double mx = (double) sumX / (double) blobXY.size();
double my = (double) sumY / (double) blobXY.size();
double u20 = 0.0; // moment central bivarié de degré 2 pour x et 0 pour y
double u02 = 0.0; // moment central bivarié de degré 0 pour x et 2 pour y
double u11 = 0.0; // moment central bivarié de degré 1 pour x et 1 pour y
double u21 = 0.0; // moment central bivarié de degré 2 pour x et 1 pour y
double u12 = 0.0; // moment central bivarié de degré 1 pour x et 2 pour y
double u30 = 0.0; // moment central bivarié de degré 3 pour x et 0 pour y
double u03 = 0.0; // moment central bivarié de degré 0 pour x et 3 pour y
for (Point p : blobXY) {
// central bivariate moments
double x = p.getX();
double y = p.getY();
u11 += (x - mx) * (y - my);
u20 += (x - mx) * (x - mx);
u02 += (y - my) * (y - my);
u21 += (x - mx) * (x - mx) * (y - my);
u12 += (x - mx) * (y - my) * (y - my);
u30 += (x - mx) * (x - mx) * (x - mx);
u03 += (y - my) * (y - my) * (y - my);
}
list.add(mx);
list.add(my);
list.add(u11);
list.add(u20);
list.add(u02);
list.add(u21);
list.add(u12);
list.add(u30);
list.add(u03);
return list;
}
}