package ie.dcu.eval; import ie.dcu.image.dt.DistanceTransform; import ie.dcu.matrix.*; /** * An evaluator that computes internal object boundary accuracy using * the Jaccard and the fuzzy Jaccard indices. * * @author Kevin McGuinness */ public class BoundaryAccuracyEvaluator extends AbstractEvaluator { /** * Name of the fuzzy accuracy measure. */ public static final String FUZZY_ACCURACY = "FBA"; /** * Name of the binary accuracy measure. */ public static final String BINARY_ACCURACY = "BBA"; /** * Default value of the sigma parameter. */ public static final double DEFAULT_SIGMA = 3.5; /** * Sigma parameter. */ private double sigma; /** * Constructor */ public BoundaryAccuracyEvaluator() { // Setup evaluator name = "Boundary Accuracy Evaluator"; description = "Compute internal object boundary accuracy " + "using the Jaccard and the fuzzy Jaccard measures"; vendor = "Kevin McGuinness"; // Add measures addMeasure(FUZZY_ACCURACY); addMeasure(BINARY_ACCURACY); // Set default sigma value sigma = DEFAULT_SIGMA; } /** * Get the sigma parameter. */ public double getSigma() { return sigma; } /** * Set the sigma parameter. * * The sigma parameter controls the tolerance of the fuzzy Jaccard * measure. * * @param sigma A value greater than zero. */ public void setSigma(double sigma) { if (sigma <= 0) { throw new IllegalArgumentException("sigma must be > 0"); } this.sigma = sigma; } /** * Run evaluator. */ public void run(ByteMatrix a, ByteMatrix b) throws IllegalArgumentException { // Check arguments check(a, b); // Find boundaries ByteMatrix boundaryA = findInternalBoundary(a); ByteMatrix boundaryB = findInternalBoundary(b); // Compute accuracy double fba = fuzzyBoundaryAccuracy(boundaryA, boundaryB); double bba = binaryBoundaryAccuracy(boundaryA, boundaryB); // Set result setResult(FUZZY_ACCURACY, fba); setResult(BINARY_ACCURACY, bba); } /** * Compute fuzzy boundary accuracy between given boundary masks. */ private double fuzzyBoundaryAccuracy( ByteMatrix boundaryA, ByteMatrix boundaryB) { // Compute distance transforms double[] dtA = distanceTransform(boundaryA).values; double[] dtB = distanceTransform(boundaryB).values; // Compute Gaussian applyGaussian(dtA); applyGaussian(dtB); // Compute fuzzy Jaccard return fuzzyJaccard(dtA, dtB); } /** * Compute binary boundary accuracy between given boundary masks. */ private double binaryBoundaryAccuracy( ByteMatrix boundaryA, ByteMatrix boundaryB) { // Operate directly on the masks return binaryJaccard(boundaryA.values, boundaryB.values); } /** * Fuzzy jaccard computation on fuzzy masks */ private double fuzzyJaccard(double[] a, double[] b) { assert (a.length == b.length); double num = 0; double den = 0; for (int i = 0; i < a.length; i++) { num += Math.min(a[i], b[i]); den += Math.max(a[i], b[i]); } if (den != 0.0) { num /= den; return num; } return 0.0; } /** * Binary Jaccard computation on binary masks. */ private double binaryJaccard(byte[] a, byte[] b) { assert (a.length == b.length); double union_v = 0; double intersection = 0; for (int i = 0; i < a.length; i++) { if (a[i] == FG || b[i] == FG) { union_v++; } if (a[i] == FG && b[i] == FG) { intersection++; } } if (union_v != 0) { return intersection / union_v; } return 0; } /** * Apply gaussian kernel to mask. */ private void applyGaussian(double[] values) { double denominator = 2.0 * sigma * sigma; for (int i = 0; i < values.length; i++) { double x = values[i]; values[i] = Math.exp(-(x*x) / denominator); } } /** * Compute Euclidean distance transform of mask. */ private DoubleMatrix distanceTransform(ByteMatrix m) { DistanceTransform dt = new DistanceTransform(); dt.init(m, BG); return dt.computeTransform(); } /** * Determine the internal boundary of the foreground object. */ private static ByteMatrix findInternalBoundary(ByteMatrix m) { ByteMatrix result = new ByteMatrix(m.rows, m.cols); for (int i = 0; i < m.rows; i++) { int k = i * m.cols; for (int j = 0; j < m.cols; j++, k++) { if (m.values[k] == FG) { // border pixels if (i == 0 || j == 0 || i == m.rows - 1 || j == m.cols - 1) { result.values[k] = FG; continue; } // pixels with neighbors in background if (m.values[k-m.cols-1] == BG || // (i-1, j-1) m.values[k-m.cols] == BG || // (i , j-1) m.values[k-m.cols+1] == BG || // (i+1, j-1) m.values[k-1] == BG || // (i-1, j ) m.values[k+1] == BG || // (i+1, j ) m.values[k+m.cols-1] == BG || // (i-1, j+1) m.values[k+m.cols] == BG || // (i , j+1) m.values[k+m.cols+1] == BG) // (i+1, j+1) { result.values[k] = FG; } else { result.values[k] = BG; } } else { result.values[k] = BG; } } } return result; } }