package fr.unistra.pelican.algorithms.statistics; import java.awt.Point; import java.util.ArrayList; import java.util.Arrays; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.AlgorithmException; import fr.unistra.pelican.BooleanImage; import fr.unistra.pelican.ByteImage; import fr.unistra.pelican.Image; import fr.unistra.pelican.algorithms.arithmetic.Inversion; import fr.unistra.pelican.algorithms.conversion.AverageChannels; import fr.unistra.pelican.algorithms.io.ImageLoader; import fr.unistra.pelican.algorithms.logical.AND; import fr.unistra.pelican.algorithms.logical.BinaryDifference; import fr.unistra.pelican.algorithms.logical.OR; import fr.unistra.pelican.algorithms.morphology.binary.BinaryDilation; import fr.unistra.pelican.algorithms.morphology.binary.geodesic.FastBinaryReconstruction; import fr.unistra.pelican.algorithms.morphology.binary.hitormiss.BinaryHST; import fr.unistra.pelican.algorithms.segmentation.flatzones.BooleanConnectedComponentsLabeling; import fr.unistra.pelican.algorithms.segmentation.labels.LabelsToBinaryMasks; import fr.unistra.pelican.algorithms.segmentation.labels.RegionSize; import fr.unistra.pelican.algorithms.visualisation.Viewer2D; import fr.unistra.pelican.util.morphology.FlatStructuringElement2D; /** * This class computes some kind of Performance Index between two segmentations * Assumes the main object crosses completely the image without hole * * @author Lefevre */ public class PerformanceIndex extends Algorithm { /** * Minimum difference */ public static final int MIN = 0; /** * Maximum difference */ public static final int MAX = 1; /** * Frechet distance */ public static final int FRECHET = 2; /** * Difference based on skeletons */ public static final int SKEL = 3; /** * specific mode to compute false positives in terms of components */ public static final int FPC = 4; /** * specific mode to compute false positives in terms of pixels */ public static final int FPP = 5; /** * pixel-based distance */ public static final int DIST = 6; /** * specific mode to compute false negatives in terms of components */ public static final int FNC = 7; /** * specific mode to compute false negatives in terms of pixels */ public static final int FNP = 8; /** * Reference image */ public Image reference; /** * Evaluated image */ public Image result; /** * The mode to use */ public int mode; /** * The output value */ public double output; /** * A flag to ensure normalization */ public boolean normalisation = false; private Image work = null; private Image reference2 = null; private Image result2 = null; /** * This method computes some kind of Performance Index between two * segmentations Assumes the main object crosses completely the image without * hole * * @param reference * Reference image * @param result * Evaluated image * @param mode * The mode to use * @return some kind of Performance Index between two segmentations Assumes * the main object crosses completely the image without hole */ public static Double exec(Image reference, Image result, Integer mode) { return (Double) new PerformanceIndex().process(reference, result, mode); } public static Double exec(Image reference, Image result, Integer mode, Boolean normalisation) { return (Double) new PerformanceIndex().process(reference, result, mode, normalisation); } /** * Constructor * */ public PerformanceIndex() { super.inputs = "reference,result,mode"; super.outputs = "output"; super.options = "normalisation"; } /* * (non-Javadoc) * * @see fr.unistra.pelican.Algorithm#launch() */ public void launch() throws AlgorithmException { // Binarisation result = new BooleanImage(result, true); reference = new BooleanImage(reference, true); if (((BooleanImage) result).isEmpty()) { output = -1; return; } int ratio = count(reference); switch (mode) { case FRECHET: td(true); break; case DIST: ratio = td(false); break; case SKEL: // Ecart entre les traits squelettisᅵs reference = BinaryHST.exec(reference); result = BinaryHST.exec(result); distance(); break; case MIN: // Ecart entre les traits distance(); break; case MAX: // MAX = MIN + resultat - intersection distance(); output += count(result2); output -= count(reference2); break; // case AVG: // AVG = (MIN + MAX) / 2 // distance(); // output += (count(result2) - count(reference2)) / 2; // break; case FPP: // Nombre de faux positifs (pixels) setup(); output = count(fp()); break; case FPC: // Nombre de faux positifs (composantes) setup(); output = fp().getBDim(); break; case FNP: // Nombre de faux nᅵgatifs (pixels) setup(); output = count(fn()); break; case FNC: // Nombre de faux nᅵgatifs (composantes) setup(); // Calcul standard // output = (Integer) new // ConnectedComponentsLabeling().processOne(1, // fn(),ConnectedComponentsLabeling.CONNEXITY8,false); // Calcul rapide output = fnc(); break; } if (normalisation && mode != FPP && mode != FPC && mode != FNP && mode != FNC && mode !=FRECHET) output /= ratio; } private int td(boolean frechet) { ArrayList<Point> list1, list2; int xdim, ydim; double sum1, sum2, val, min,max; // Initialisation image 1 list1 = new ArrayList<Point>(); xdim = reference.getXDim(); ydim = reference.getYDim(); for (int y = 0; y < ydim; y++) for (int x = 0; x < xdim; x++) if (reference.getPixelXYBoolean(x, y)) list1.add(new Point(x, y)); // Initialisation image 2 list2 = new ArrayList<Point>(); xdim = result.getXDim(); ydim = result.getYDim(); for (int y = 0; y < ydim; y++) for (int x = 0; x < xdim; x++) if (result.getPixelXYBoolean(x, y)) list2.add(new Point(x, y)); // Calcul distances image 1 max=0; sum1 = 0; for (Point p1 : list1) { min = Double.MAX_VALUE; for (Point p2 : list2) { val = p1.distance(p2); if (val < min) min = val; } sum1 += min; if(min>max) max=min; } sum2 = 0; for (Point p2 : list2) { min = Double.MAX_VALUE; for (Point p1 : list1) { val = p1.distance(p2); if (val < min) min = val; } sum2 += min; if(min>max) max=min; } if(!frechet) output = sum1 + sum2; else output=max; return (list1.size() + list2.size()); // TODO: autres possibilitᅵs ? sum1/size1 ou sum2/size2 ou les 2 ? } private Image fn() { Image missed = reference.copyImage(false); int cc = 0; // CC de result2 Image labels = LabelsToBinaryMasks.exec(BooleanConnectedComponentsLabeling.exec( result2, BooleanConnectedComponentsLabeling.CONNEXITY8, false)); Image[] tab = new Image[labels.getBDim()]; for (int l = 0; l < labels.getBDim(); l++) tab[l] = labels.getImage4D(l, Image.B); // CC de reference non trouvᅵs Image holes = LabelsToBinaryMasks.exec(BooleanConnectedComponentsLabeling.exec( BinaryDifference.exec(reference, reference2), BooleanConnectedComponentsLabeling.CONNEXITY8, false)); boolean ok, bord; for (int h = 0; h < holes.getBDim(); h++) { Image work = holes.getImage4D(h, Image.B); ok = false; bord = false; // Cas particulier si le faux nᅵgatif est sur le bord if (bords(work) > 0) bord = true; Image mask = OR.exec(work, result2); mask = FastBinaryReconstruction.exec(work, mask); mask = AND.exec(mask, result2); // On teste le faux nᅵgatif avec l'ensemble des CC for (int l = 0; l < tab.length && !ok; l++) { if (subset(mask, tab[l])) // Si le faux nᅵgatif est sur le bord, le CC doit l'ᅵtre // aussi if (!bord || bords(tab[l]) > 0) ok = true; } // On conserve le faux nᅵgatif s'il n'ᅵtait pas dᅵlimitᅵ par l'un // des CC if (!ok) { missed = OR.exec(missed, work); cc++; } } return missed; } private Image fp() { return LabelsToBinaryMasks.exec(BooleanConnectedComponentsLabeling.exec( BinaryDifference.exec(result, result2), BooleanConnectedComponentsLabeling.CONNEXITY8, false)); } private int fnc() { return (Integer) new BooleanConnectedComponentsLabeling().processOne(1, result2, BooleanConnectedComponentsLabeling.CONNEXITY8, false) - bords(result2); } private void setup() { reference2 = AND.exec(reference, result); result2 = FastBinaryReconstruction.exec(reference2, result); } private void distance() { setup(); // Cas particulier lorsque les traits sont disjoints if (((BooleanImage) reference2).isEmpty()) { System.err.println("Disjoints sets"); Image labels = LabelsToBinaryMasks.exec(BooleanConnectedComponentsLabeling.exec( Inversion.exec(OR.exec(reference, result)), BooleanConnectedComponentsLabeling.CONNEXITY4, false)); reference = BinaryDilation.exec(reference, FlatStructuringElement2D .createSquareFlatStructuringElement(3)); result2 = BinaryDilation.exec(result, FlatStructuringElement2D .createSquareFlatStructuringElement(3)); work = result.copyImage(false); Image work2 = result.copyImage(false); for (int l = 0; l < labels.getBDim(); l++) { Image tmp = labels.getImage4D(l, Image.B); Image tmp2 = AND.exec(FastBinaryReconstruction.exec(reference, tmp), result2); if (!((BooleanImage) tmp2).isEmpty()) { work = OR.exec(work, tmp); work2 = OR.exec(work2, AND.exec(result, FastBinaryReconstruction .exec(tmp2, result2))); } } result2 = work2; output = count(work); // Viewer2D.exec(work); // Viewer2D.exec(result2); // Viewer2D.exec(reference2); } else { Image labels = BooleanConnectedComponentsLabeling.exec(Inversion.exec(OR.exec( reference, result2)), BooleanConnectedComponentsLabeling.CONNEXITY4, false); // Viewer2D.exec(Inversion.exec(OR.exec(reference, result2))); // Viewer2D.exec(result2); // Viewer2D.exec(reference2); int tab[] = RegionSize.exec(labels); tab[0] = 0; Arrays.sort(tab); for (int t = 0; t < tab.length - 2; t++) output += tab[t]; } } private int count(Image im) { int s = 0; for (int p = 0; p < im.size(); p++) if (im.getPixelBoolean(p)) s++; return s; } private static int bords(Image im) { int xdim = im.getXDim(); int ydim = im.getYDim(); int bords = 0; for (int x = 0; x < xdim; x++) { if (im.getPixelXYBoolean(x, 0)) bords++; if (im.getPixelXYBoolean(x, ydim - 1)) bords++; } for (int y = 0; y < ydim; y++) { if (im.getPixelXYBoolean(0, y)) bords++; if (im.getPixelXYBoolean(xdim - 1, y)) bords++; } return bords; } private static boolean subset(Image i1, Image i2) { if (i1.size() != i2.size()) return false; for (int p = 0; p < i1.size(); p++) if (i1.getPixelBoolean(p) && !i2.getPixelBoolean(p)) return false; return true; } public static void main(String args[]) { String path = "/home/lefevre/data/unversioned"; String f1 = path + "/pi1.png"; String f2 = path + "/pi9.png"; if (args.length == 2) { f1 = args[0]; f2 = args[1]; } Image pi1 = AverageChannels.exec(ImageLoader.exec(f1)); Image pi2 = AverageChannels.exec(ImageLoader.exec(f2)); Viewer2D.exec(pi1, "pi1"); Viewer2D.exec(pi2, "pi2"); System.out.println("size 1:" + ((ByteImage) pi1).volume()); System.out.println("size 2:" + ((ByteImage) pi2).volume()); System.out.println("PI MIN =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.MIN, false) + " / " + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.MIN, true)); System.out.println("PI DIST =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.DIST, false) + " / " + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.DIST, true)); System.out.println("PI MAX =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.MAX, false) + " / " + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.MAX, true)); // System.out.println("PI SKEL =" // + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.SKEL, false) + " / " // + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.SKEL, true)); System.out.println("PI FRECHET =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.FRECHET)); System.out.println("PI FPC =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.FPC)); System.out.println("PI FPP =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.FPP)); System.out.println("PI FNC =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.FNC)); System.out.println("PI FNP =" + PerformanceIndex.exec(pi1, pi2, PerformanceIndex.FNP)); } }