package fr.unistra.pelican.algorithms.statistics; import java.util.Properties; 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.conversion.AverageChannels; import fr.unistra.pelican.algorithms.io.ImageLoader; import fr.unistra.pelican.algorithms.logical.AND; import fr.unistra.pelican.algorithms.logical.CompareImage; import fr.unistra.pelican.algorithms.logical.OR; import fr.unistra.pelican.algorithms.visualisation.Viewer2D; /** * This class computes Confusion Matrix and related statistics between two * segmentations * * @author Lefevre */ public class ConfusionMatrix extends Algorithm { /** * Vrais positifs */ public static final int VP = 0; /** * Vrais négatifs */ public static final int VN = 1; /** * Faux positifs */ public static final int FP = 2; /** * Faux négatifs */ public static final int FN = 3; /** * Qualité producteur */ public static final int PROD = 4; /** * Qualité utilisateur */ public static final int UTIL = 5; /** * Precision globale */ public static final int PRECISION = 6; /** * Indice kappa */ public static final int KAPPA = 7; /** * Sensitivity (recall) */ public static final int SENSITIVITY = 8; /** * Specificity */ public static final int SPECIFICITY = 9; /** * Jaccard similarity index (surface distance) */ public static final int JACCARD = 10; /** * Dice coefficient (F-measure, Soerensen index) */ public static final int DICE = 11; /** * Reference image */ public Image reference; /** * Evaluated image */ public Image result; /** * (optional) display the stats */ public boolean display = false; /** * (optional) store properties as String objects */ public boolean string = false; /** * The output value as a set of properties */ public Properties output; /** * This class computes Confusion Matrix and related statistics between two * segmentations * * @param reference * Reference image * @param result * Evaluated image * @return various measures stored in a Properties object */ public static Properties exec(Image reference, Image result) { return (Properties) new ConfusionMatrix().process(reference, result); } public static Properties exec(Image reference, Image result, Boolean display) { return (Properties) new ConfusionMatrix().process(reference, result, display); } public static Properties exec(Image reference, Image result, Boolean display, Boolean string) { return (Properties) new ConfusionMatrix().process(reference, result, display, string); } /** * Constructor */ public ConfusionMatrix() { super.inputs = "reference,result"; super.outputs = "output"; super.options = "display,string"; } /* * (non-Javadoc) * * @see fr.unistra.pelican.Algorithm#launch() */ public void launch() throws AlgorithmException { StringBuffer out = new StringBuffer(); output = new Properties(); // Binarisation result = new BooleanImage(result, true); reference = new BooleanImage(reference, true); double total = result.size(); // Classif double resultFG = 0; for (int p = 0; p < result.size(); p++) if (result.getPixelBoolean(p)) resultFG++; double resultBG = result.size() - resultFG; // Terrain double referenceFG = 0; for (int p = 0; p < reference.size(); p++) if (reference.getPixelBoolean(p)) referenceFG++; double referenceBG = result.size() - referenceFG; // Vrais positifs Image vpImg = AND.exec(result, reference); // correct int vp = 0; for (int p = 0; p < vpImg.size(); p++) if (vpImg.getPixelBoolean(p)) vp++; // Faux positifs Image fpImg = CompareImage.exec(result, reference, CompareImage.SUP); int fp = 0; for (int p = 0; p < fpImg.size(); p++) if (fpImg.getPixelBoolean(p)) fp++; // Faux negatifs Image fnImg = CompareImage.exec(reference, result, CompareImage.SUP); int fn = 0; for (int p = 0; p < fnImg.size(); p++) if (fnImg.getPixelBoolean(p)) fn++; // Vrais négatifs Image vnImg = OR.exec(result, reference); // fond int vn = 0; for (int p = 0; p < vnImg.size(); p++) if (!vnImg.getPixelBoolean(p)) vn++; out.append("\nEvaluation par pixels"); // Matrice de confusion out.append("\n**** Matrice de confusion ****"); out.append("\n fg bg total"); out.append("\nfg " + vp + " " + fp + " " + (int) referenceFG); out.append("\nbg " + fn + " " + vn + " " + (int) referenceBG); out.append("\ntotal " + (int) resultFG + " " + (int) resultBG); // Exactitude producteur out.append("\n**** Qualité producteur ****"); out.append("\n\t fg = " + vp / resultFG); // sensitivity double sensitivity = vp / resultFG; out.append("\n\t bg = " + vn / resultBG); // specificity double specificity = vn / resultBG; double prod = vp / resultFG; // Exactitude utilisateur out.append("\n**** Qualité utilisateur ****"); out.append("\n\t fg = " + vp / referenceFG); out.append("\n\t bg = " + vn / referenceBG); double util = vp / referenceFG; // Precision globale double precision = (vp + vn) / total; out.append("\n**** Mesures globales ****"); out.append("\n\t précision = " + precision); // Indice Kappa double chance = (referenceFG * resultFG) / (total * total) + (referenceBG * resultBG) / (total * total); double kappa = (precision - chance) / (1 - chance); out.append("\n\t indice kappa = " + kappa); // Jaccard similarity double jaccard = ((double)vp) / (fp + vp + fn); // Dice coefficient double dice = ((double)(2 * vp)) / (fp + vp + vp + fn); out.append("\n"); if (display) System.out.println(out); // Enregistrement des résultats output.put(string ? "VP" : VP, string ? Integer.toString(vp) : vp); output.put(string ? "VN" : VN, string ? Integer.toString(vn) : vn); output.put(string ? "FP" : FP, string ? Integer.toString(fp) : fp); output.put(string ? "FN" : FN, string ? Integer.toString(fn) : fn); output.put(string ? "SPECIFICITY" : SPECIFICITY, string ? Double .toString(specificity) : specificity); output.put(string ? "OVERALL ACCURACY" : PRECISION, string ? Double .toString(precision) : precision); output.put(string ? "PROD" : PROD, string ? Double.toString(prod) : prod); output.put(string ? "UTIL/PRECISION" : UTIL, string ? Double.toString(util) : util); output.put(string ? "SENSITIVITY/RECALL" : SENSITIVITY, string ? Double .toString(sensitivity) : sensitivity); output.put(string ? "KAPPA" : KAPPA, string ? Double.toString(kappa) : kappa); output.put(string ? "JACCARD" : JACCARD, string ? Double .toString(jaccard) : jaccard); output.put(string ? "DICE" : DICE, string ? Double.toString(dice) : dice); } public static void main(String args[]) { String path = "/home/lefevre/data/unversioned"; String f1 = path + "/pi1.png"; String f2 = path + "/pi2.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, f1); Viewer2D.exec(pi2, f2); System.out.println("size 1:" + ((ByteImage) pi1).volume()); System.out.println("size 2:" + ((ByteImage) pi2).volume()); Properties prop = ConfusionMatrix.exec(pi1, pi2); System.out.println("VP=" + prop.get(VP)); System.out.println("VN=" + prop.get(VN)); System.out.println("FP=" + prop.get(FP)); System.out.println("FN=" + prop.get(FN)); System.out.println("PROD=" + prop.get(PROD)); System.out.println("UTIL=" + prop.get(UTIL)); System.out.println("PRECISION=" + prop.get(PRECISION)); System.out.println("KAPPA=" + prop.get(KAPPA)); } }