diff --git a/SLIC/.gitignore b/SLIC/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..966b6392642cecda2fe31cbad66206edf7296202 --- /dev/null +++ b/SLIC/.gitignore @@ -0,0 +1,6 @@ +/.gradle/ +/.settings/ +/bin/ +/.classpath +/.project +/build/ diff --git a/SLIC/build.gradle b/SLIC/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..93ab67e0809e71e9219144758b3ec03fb09c9801 --- /dev/null +++ b/SLIC/build.gradle @@ -0,0 +1,60 @@ +apply plugin: 'java' +apply plugin: 'eclipse' + +version = '0.1.0' +println project.name + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + compile 'org.slf4j:slf4j-api:1.7.21' + + compile files("${System.env.ICY_HOME}/icy.jar") // Icy core + compile files("${System.env.ICY_HOME}/lib/vecmath.jar") // javax vecmath + compile files("${System.env.ICY_HOME}/lib/bioformats.jar") // bioformats + compile files("${System.env.ICY_HOME}/plugins/adufour/ezplug/EzPlug.jar") // EzPlug + compile files("${System.env.ICY_HOME}/plugins/adufour/blocks/Blocks.jar") // Blocks + compile files("${System.env.ICY_HOME}/plugins/adufour/protocols/Protocols.jar") // Protocols + + testCompile 'junit:junit:4.12' +} + +// In this section source and test files are declared +sourceSets { + main { + java { + srcDirs = ['src'] + resources.srcDirs = ['res'] + } + test { + java.srcDirs = ['test'] + resources.srcDirs = ['res'] + } + + } +} + +// In this section we add the icy nature of the project in eclipse +eclipse { + project { + natures += ['icy.icy4eclipse.core.icynature'] + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} \ No newline at end of file diff --git a/SLIC/src/algorithms/danyfel80/islic/CIELab.java b/SLIC/src/algorithms/danyfel80/islic/CIELab.java new file mode 100644 index 0000000000000000000000000000000000000000..4c6a7e1b2ee462e290326bdab6337af65bd7e570 --- /dev/null +++ b/SLIC/src/algorithms/danyfel80/islic/CIELab.java @@ -0,0 +1,192 @@ +package algorithms.danyfel80.islic; + +public class CIELab { + + public static class XYZ { + + private static final double[][] M = { { 0.4124, 0.3576, 0.1805 }, { 0.2126, 0.7152, 0.0722 }, + { 0.0193, 0.1192, 0.9505 } }; + private static final double[][] Mi = { { 3.2406, -1.5372, -0.4986 }, { -0.9689, 1.8758, 0.0415 }, + { 0.0557, -0.2040, 1.0570 } }; + + public static double[] fromRGB(int[] rgb) { + return fromRGB(rgb[0], rgb[1], rgb[2]); + } + + public static double[] fromRGB(int R, int G, int B) { + double[] result = new double[3]; + + // convert 0..255 into 0..1 + double r = R / 255.0; + double g = G / 255.0; + double b = B / 255.0; + + // assume sRGB + if (r <= 0.04045) { + r = r / 12.92; + } else { + r = Math.pow(((r + 0.055) / 1.055), 2.4); + } + if (g <= 0.04045) { + g = g / 12.92; + } else { + g = Math.pow(((g + 0.055) / 1.055), 2.4); + } + if (b <= 0.04045) { + b = b / 12.92; + } else { + b = Math.pow(((b + 0.055) / 1.055), 2.4); + } + + r *= 100.0; + g *= 100.0; + b *= 100.0; + + // [X Y Z] = [r g b][M] + result[0] = (r * M[0][0]) + (g * M[0][1]) + (b * M[0][2]); + result[1] = (r * M[1][0]) + (g * M[1][1]) + (b * M[1][2]); + result[2] = (r * M[2][0]) + (g * M[2][1]) + (b * M[2][2]); + + return result; + } + + public static int[] toRGB(double[] xyz) { + return toRGB(xyz[0], xyz[1], xyz[2]); + } + + public static int[] toRGB(double X, double Y, double Z) { + int[] result = new int[3]; + + double x = X / 100.0; + double y = Y / 100.0; + double z = Z / 100.0; + + // [r g b] = [X Y Z][Mi] + double r = (x * Mi[0][0]) + (y * Mi[0][1]) + (z * Mi[0][2]); + double g = (x * Mi[1][0]) + (y * Mi[1][1]) + (z * Mi[1][2]); + double b = (x * Mi[2][0]) + (y * Mi[2][1]) + (z * Mi[2][2]); + + // assume sRGB + if (r > 0.0031308) { + r = ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055); + } else { + r = (r * 12.92); + } + if (g > 0.0031308) { + g = ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055); + } else { + g = (g * 12.92); + } + if (b > 0.0031308) { + b = ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055); + } else { + b = (b * 12.92); + } + + r = (r < 0) ? 0 : r; + g = (g < 0) ? 0 : g; + b = (b < 0) ? 0 : b; + + // convert 0..1 into 0..255 + result[0] = (int) Math.round(r * 255); + result[1] = (int) Math.round(g * 255); + result[2] = (int) Math.round(b * 255); + + return result; + } + + } + + private static final double[] whitePoint = { 95.0429, 100.0, 108.8900 }; // D65 + + public static double[] fromRGB(int[] rgb) { + return fromXYZ(XYZ.fromRGB(rgb)); + } + + public static double[] fromRGB(int r, int g, int b) { + return fromXYZ(XYZ.fromRGB(r, g, b)); + } + + private static double[] fromXYZ(double[] xyz) { + return fromXYZ(xyz[0], xyz[1], xyz[2]); + } + + public static double[] fromXYZ(double X, double Y, double Z) { + double x = X / whitePoint[0]; + double y = Y / whitePoint[1]; + double z = Z / whitePoint[2]; + + if (x > 0.008856) { + x = Math.pow(x, 1.0 / 3.0); + } + else { + x = (7.787 * x) + (16.0 / 116.0); + } + if (y > 0.008856) { + y = Math.pow(y, 1.0 / 3.0); + } + else { + y = (7.787 * y) + (16.0 / 116.0); + } + if (z > 0.008856) { + z = Math.pow(z, 1.0 / 3.0); + } + else { + z = (7.787 * z) + (16.0 / 116.0); + } + + double[] result = new double[3]; + + result[0] = (116.0 * y) - 16.0; + result[1] = 500.0 * (x - y); + result[2] = 200.0 * (y - z); + + return result; + } + + public static int[] toRGB(double[] lab) { + return XYZ.toRGB(toXYZ(lab)); + } + + public static int[] toRGB(double L, double a, double b) { + return XYZ.toRGB(toXYZ(L, a, b)); + } + + public static double[] toXYZ(double[] lab) { + return toXYZ(lab[0], lab[1], lab[2]); + } + + public static double[] toXYZ(double L, double a, double b) { + double[] result = new double[3]; + + double y = (L + 16.0) / 116.0; + double y3 = Math.pow(y, 3.0); + double x = (a / 500.0) + y; + double x3 = Math.pow(x, 3.0); + double z = y - (b / 200.0); + double z3 = Math.pow(z, 3.0); + + if (y3 > 0.008856) { + y = y3; + } else { + y = (y - (16.0 / 116.0)) / 7.787; + } + if (x3 > 0.008856) { + x = x3; + } else { + x = (x - (16.0 / 116.0)) / 7.787; + } + if (z3 > 0.008856) { + z = z3; + } else { + z = (z - (16.0 / 116.0)) / 7.787; + } + + // results in 0...100 + result[0] = x * whitePoint[0]; + result[1] = y * whitePoint[1]; + result[2] = z * whitePoint[2]; + + return result; + } +} diff --git a/SLIC/src/algorithms/danyfel80/islic/SLICTask.java b/SLIC/src/algorithms/danyfel80/islic/SLICTask.java new file mode 100644 index 0000000000000000000000000000000000000000..dc7aac788141476446b5830a42d03c708203e4d6 --- /dev/null +++ b/SLIC/src/algorithms/danyfel80/islic/SLICTask.java @@ -0,0 +1,554 @@ +/* + * Copyright 2010-2016 Institut Pasteur. + * + * This file is part of Icy. + * + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <http://www.gnu.org/licenses/>. + */ +package algorithms.danyfel80.islic; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.vecmath.Point2i; +import javax.vecmath.Point3d; +import javax.vecmath.Point3i; +import javax.vecmath.Vector3d; + +import icy.image.IcyBufferedImage; +import icy.roi.BooleanMask2D; +import icy.roi.ROI; +import icy.sequence.Sequence; +import icy.type.DataType; +import icy.type.collection.array.Array2DUtil; +import plugins.kernel.roi.roi2d.ROI2DArea; + +/** + * @author Daniel Felipe Gonzalez Obando + */ +public class SLICTask { + + private static final double EPSILON = 0.25; + static int MAX_ITERATIONS = 10; + static double ERROR_THRESHOLD = 1e-2; + + private Sequence sequence; + private int S; + private int Sx; + private int Sy; + private int SPnum; + private double rigidity; + + private boolean computeROIs; + private List<ROI> rois; + private Sequence superPixelsResult; + + private Map<Point3i, double[]> colorLUT; + private Map<Point2i, Double> distanceLUT; + + public SLICTask(Sequence sequence, int SPSize, double rigidity, boolean computeROIs) { + this.sequence = sequence; + this.S = SPSize; + this.Sx = (sequence.getWidth() + S / 2 - 1) / S; + this.Sy = (sequence.getHeight() + S / 2 - 1) / S; + this.SPnum = Sx * Sy; + this.rigidity = rigidity; + + this.computeROIs = computeROIs; + + colorLUT = new HashMap<>(); + distanceLUT = new HashMap<>(); + } + + public void execute() { + + double[] ls = new double[SPnum]; + double[] as = new double[SPnum]; + double[] bs = new double[SPnum]; + + double[] cxs = new double[SPnum]; + double[] cys = new double[SPnum]; + + double[][] sequenceData = Array2DUtil.arrayToDoubleArray(sequence.getDataXYC(0, 0), sequence.isSignedDataType()); + + initialize(ls, as, bs, cxs, cys, sequenceData); + + int[] clusters = new int[sequence.getWidth() * sequence.getHeight()]; + double[] distances = Arrays.stream(clusters).mapToDouble(i -> Double.MAX_VALUE).toArray(); + + int iterations = 0; + double error = 0; + do { + assignClusters(sequenceData, clusters, distances, ls, as, bs, cxs, cys); + error = updateClusters(sequenceData, clusters, ls, as, bs, cxs, cys); + iterations++; + System.out.format("Iteration=%d, Error=%f%n", iterations, error); + } while (error > ERROR_THRESHOLD && iterations < MAX_ITERATIONS); + System.out.format("End of iterations%n"); + + computeSuperpixels(sequenceData, clusters, ls, as, bs, cxs, cys); + } + + private void initialize(double[] ls, double[] as, double[] bs, double[] cxs, double[] cys, double[][] sequenceData) { + + IntStream.range(0, SPnum).forEach(sp -> { + int x, y, yOff, bestx, besty, bestyOff, i, j, yjOff; + double bestGradient, candidateGradient; + + int spx = sp % Sx; + int spy = sp / Sx; + + x = S / 2 + (spx) * S; + y = S / 2 + (spy) * S; + yOff = y * sequence.getWidth(); + + cxs[sp] = bestx = x; + cys[sp] = besty = y; + bestyOff = yOff; + bestGradient = Double.MAX_VALUE; + + for (j = -1; j <= 1; j++) { + if (y + j >= 0 && y + j < sequence.getHeight()) { + yjOff = (y + j) * sequence.getWidth(); + for (i = -1; i <= 1; i++) { + if (x >= 0 && x < sequence.getWidth() && x + i >= 0 && x + i < sequence.getWidth()) { + candidateGradient = getGradient(sequenceData, x + yOff, (x + i) + yjOff); + if (candidateGradient < bestGradient) { + bestx = x + i; + besty = y + j; + bestyOff = yjOff; + bestGradient = candidateGradient; + } + } + } + } + } + + cxs[sp] = bestx; + cys[sp] = besty; + int bestPos = bestx + bestyOff; + + double[] bestLAB = getCIELab(sequenceData, bestPos); + ls[sp] = bestLAB[0]; + as[sp] = bestLAB[1]; + bs[sp] = bestLAB[2]; + + }); + + } + + private double getGradient(double[][] data, int pos1, int pos2) { + int[] valRGB1 = new int[3]; + int[] valRGB2 = new int[3]; + IntStream.range(0, 3).forEach(c -> { + try { + valRGB1[c] = (int) Math.round(255 * (data[c % sequence.getSizeC()][pos1] / sequence.getDataTypeMax())); + valRGB2[c] = (int) Math.round(255 * (data[c % sequence.getSizeC()][pos2] / sequence.getDataTypeMax())); + } catch (Exception e) { + throw e; + } + }); + + double[] val1 = CIELab.fromRGB(valRGB1); + double[] val2 = CIELab.fromRGB(valRGB2); + + double avgGrad = IntStream.range(0, 3).mapToDouble(c -> val2[c] - val1[c]).average().getAsDouble(); + + return avgGrad; + } + + private double[] getCIELab(double[][] sequenceData, int pos) { + Point3i rgbPoint = new Point3i(); + int[] rgbVal = new int[3]; + IntStream.range(0, 3).forEach(c -> { + rgbVal[c] = (int) Math.round(255 * (sequenceData[c % sequence.getSizeC()][pos] / sequence.getDataTypeMax())); + }); + rgbPoint.set(rgbVal); + + double[] LAB = colorLUT.get(rgbPoint); + if (LAB == null) { + int[] RGB = new int[3]; + IntStream.range(0, 3).forEach(c -> { + RGB[c] = (int) Math.round(255 * (sequenceData[c % sequence.getSizeC()][pos] / sequence.getDataTypeMax())); + }); + + LAB = CIELab.fromRGB(RGB); + colorLUT.put(rgbPoint, LAB); + } + return LAB; + } + + private void assignClusters(double[][] sequenceData, int[] clusters, double[] distances, double[] ls, double[] as, + double[] bs, double[] cxs, double[] cys) { + // Each cluster + IntStream.range(0, SPnum).forEach(k -> { + double ckx = cxs[k], cky = cys[k], lk = ls[k], ak = as[k], bk = bs[k]; + int posk = ((int) ckx) + ((int) cky) * sequence.getWidth(); + distances[posk] = 0; + clusters[posk] = k; + + // 2Sx2S window assignment + int j, i, yOff; + for (j = -S; j < S; j++) { + int ciy = (int) (cky + j); + if (ciy >= 0 && ciy < sequence.getHeight()) { + yOff = ciy * sequence.getWidth(); + for (i = -S; i < S; i++) { + int cix = (int) (ckx + i); + if (cix >= 0 && cix < sequence.getWidth()) { + int posi = cix + yOff; + double[] LABi = getCIELab(sequenceData, posi); + double li = LABi[0]; + double ai = LABi[1]; + double bi = LABi[2]; + + double di = getDistance(ckx, cky, lk, ak, bk, i, j, li, ai, bi); + if (di < distances[posi]) { + distances[posi] = di; + clusters[posi] = clusters[posk]; + } + } + } + } + } + }); + } + + private double getDistance(double ckx, double cky, double lk, double ak, double bk, int dx, int dy, double li, + double ai, double bi) { + + double diffl, diffa, diffb; + diffl = li - lk; + diffa = ai - ak; + diffb = bi - bk; + + double dc = Math.sqrt(diffl * diffl + diffa * diffa + diffb * diffb); + Point2i dPt = new Point2i((int) Math.min(dx, dy), (int) Math.max(dx, dy)); + Double ds = distanceLUT.get(dPt); + if (ds == null) { + ds = Math.sqrt(dx * dx + dy * dy); + distanceLUT.put(dPt, ds); + } + return Math.sqrt(dc * dc + (ds * ds) * (rigidity * rigidity * S)); + } + + private double updateClusters(double[][] sequenceData, int[] clusters, double[] ls, double[] as, double[] bs, + double[] cxs, double[] cys) { + + double[] newls = new double[SPnum]; + double[] newas = new double[SPnum]; + double[] newbs = new double[SPnum]; + double[] newcxs = new double[SPnum]; + double[] newcys = new double[SPnum]; + int[] cants = new int[SPnum]; + + // int blockSize = 500; + // IntStream.iterate(0, sy -> sy+blockSize).limit((int)Math.ceil(sequence.getHeight()/blockSize)).forEach(y0->{ + // int y1 = Math.min(y0+blockSize, sequence.getHeight() - y0); + // IntStream.iterate(0, sx -> sx+blockSize).limit((int)Math.ceil(sequence.getWidth()/blockSize)).forEach(x0->{ + // int x1 = Math.min(x0+blockSize, sequence.getWidth() - x0); + // }); + // }); + for (int y = 0, yOff; y < sequence.getHeight(); y++) { + yOff = y * sequence.getWidth(); + for (int x = 0, pos; x < sequence.getWidth(); x++) { + pos = x + yOff; + int sp = clusters[pos]; + double[] lab = getCIELab(sequenceData, pos); + + cants[sp]++; + + newls[sp] += (lab[0] - newls[sp]) / cants[sp]; + newas[sp] += (lab[1] - newas[sp]) / cants[sp]; + newbs[sp] += (lab[2] - newbs[sp]) / cants[sp]; + newcxs[sp] += (x - newcxs[sp]) / cants[sp]; + newcys[sp] += (y - newcys[sp]) / cants[sp]; + } + } + + double error = IntStream.range(0, SPnum).mapToDouble(sp -> { + //double diffl = ls[sp] - newls[sp]; + //double diffa = as[sp] - newas[sp]; + //double diffb = bs[sp] - newbs[sp]; + double diffx = cxs[sp] - newcxs[sp]; + double diffy = cys[sp] - newcys[sp]; + + ls[sp] = newls[sp]; + as[sp] = newas[sp]; + bs[sp] = newbs[sp]; + cxs[sp] = newcxs[sp]; + cys[sp] = newcys[sp]; + + return Math.sqrt(diffx * diffx + + diffy * diffy/* + diffl * diffl + diffa * diffa + diffb * diffb */); + }).sum() / SPnum; + + return error; + } + + private void computeSuperpixels(double[][] sequenceData, int[] clusters, double[] ls, double[] as, double[] bs, + double[] cxs, double[] cys) { + + boolean[] visited = new boolean[clusters.length]; + int[] finalClusters = new int[clusters.length]; + + List<Point3d> labs = new ArrayList<>(SPnum); + List<Double> areas = new ArrayList<>(SPnum); + List<Point2i> firstPoints = new ArrayList<>(SPnum); + + // fill known clusters + AtomicInteger usedLabels = new AtomicInteger(0); + IntStream.range(0, SPnum).forEach(i -> { + Point3d labCenter = new Point3d(); + AtomicInteger area = new AtomicInteger(0); + Point2i p = new Point2i((int) Math.round(cxs[i]), (int) Math.round(cys[i])); + int pPos = p.x + p.y * sequence.getWidth(); + + if (clusters[pPos] == i && !visited[pPos]) { + findAreaAndColor(sequenceData, clusters, finalClusters, visited, p, labCenter, area, + usedLabels.getAndIncrement()); + labs.add(labCenter); + areas.add(area.get() / (double) (S * S)); + firstPoints.add(p); + } + + }); + + int firstUnknownCluster = usedLabels.get(); + + // fill independent clusters + for (int y = 0; y < sequence.getHeight(); y++) { + int yOff = y * sequence.getWidth(); + for (int x = 0; x < sequence.getWidth(); x++) { + int pos = x + yOff; + if (!visited[pos]) { + Point3d labCenter = new Point3d(); + AtomicInteger area = new AtomicInteger(0); + Point2i p = new Point2i(x, y); + + findAreaAndColor(sequenceData, clusters, finalClusters, visited, p, labCenter, area, + usedLabels.getAndIncrement()); + + labs.add(labCenter); + areas.add(area.get() / (double) (S * S)); + firstPoints.add(p); + } + } + } + + //System.out.println("unvisited = " + IntStream.range(0, visited.length).filter(i -> visited[i] == false).sum()); + + // find neighbors and merge independent clusters + boolean[] mergedClusters = new boolean[usedLabels.get()]; + int[] mergedRefs = IntStream.range(0, usedLabels.get()).toArray(); + IntStream.range(firstUnknownCluster, usedLabels.get()).forEach(i -> { + List<Integer> neighbors = findNeighbors(finalClusters, visited, firstPoints.get(i)); + if (neighbors.size() > 0) { + int bestNeighbour = neighbors.get(0); + double bestL = Double.MAX_VALUE; + + //boolean found = false; + for (Integer j: neighbors) { + if (j < i) { + //found = true; + double l = computeL(labs, areas, i, j); + if (l < bestL) { + bestNeighbour = j; + bestL = l; + } + } + } + /* + * if (!found) { for (Integer j: neighbors) { double l = computeL(labs, + * areas, i, j); if (l < bestL) { bestNeighbour = j; bestL = l; } } } + * + * if (found) { + */ + double rArea = areas.get(i); + double relSPSize = rArea / 4; + relSPSize *= relSPSize; + + double coeff = relSPSize * (1.0 + bestL); + if (coeff < EPSILON) { + mergedClusters[i] = true; + mergedRefs[i] = bestNeighbour; + } + // } else { + // mergedClusters[i] = true; + // mergedRefs[i] = bestNeighbour; + // } + } else { + System.err.format("Cluster at (%d, %d) has no neighbors", firstPoints.get(i).x, firstPoints.get(i).y); + } + }); + IntStream.range(firstUnknownCluster, usedLabels.get()).forEach(i -> { + if (mergedClusters[i]) { + int appliedLabel = i; + while (appliedLabel != mergedRefs[appliedLabel]) { + appliedLabel = mergedRefs[appliedLabel]; + } + findAreaAndColor(sequenceData, clusters, finalClusters, visited, firstPoints.get(i), new Point3d(), + new AtomicInteger(0), appliedLabel); + } + + }); + + if (this.computeROIs) { + // Create and add ROIs to sequence + this.rois = new ArrayList<>(); + IntStream.range(0, usedLabels.get()).forEach(i -> { + if (!mergedClusters[i]) { + ROI2DArea roi = defineROI(finalClusters, firstPoints.get(i), labs.get(i)); + rois.add(roi); + } + }); + sequence.addROIs(rois, false); + } else { + List<int[]> rgbs = labs.stream().map(lab -> CIELab.toRGB(lab.x, lab.y, lab.z)).collect(Collectors.toList()); + superPixelsResult = new Sequence( + new IcyBufferedImage(sequence.getWidth(), sequence.getHeight(), 3, DataType.UBYTE)); + + superPixelsResult.beginUpdate(); + double[][] spData = Array2DUtil.arrayToDoubleArray(superPixelsResult.getDataXYC(0, 0), + superPixelsResult.isSignedDataType()); + for (int y = 0, yOff; y < sequence.getHeight(); y++) { + yOff = y * sequence.getWidth(); + for (int x = 0; x < sequence.getWidth(); x++) { + final int pos = x + yOff; + int[] rgbVal = rgbs.get(finalClusters[pos]); + + IntStream.range(0, 3).forEach(c -> { + spData[c][pos] = rgbVal[c]; + }); + + } + } + Array2DUtil.doubleArrayToArray(spData, superPixelsResult.getDataXYC(0, 0)); + superPixelsResult.dataChanged(); + superPixelsResult.endUpdate(); + } + } + + private void findAreaAndColor(double[][] sequenceData, int[] clusters, int[] newClusters, boolean[] visited, + Point2i p, Point3d labCenter, AtomicInteger area, int label) { + int posp = p.x + p.y * sequence.getWidth(); + area.set(0); + labCenter.set(0, 0, 0); + + Deque<Point2i> q = new LinkedList<>(); + int val = clusters[posp]; + + visited[posp] = true; + q.add(p); + + while (!q.isEmpty()) { + Point2i pti = q.pop(); + int posi = pti.x + pti.y * sequence.getWidth(); + + newClusters[posi] = label; + area.getAndIncrement(); + double[] labi = getCIELab(sequenceData, posi); + labCenter.x += (labi[0] - labCenter.x) / area.get(); + labCenter.y += (labi[1] - labCenter.y) / area.get(); + labCenter.z += (labi[2] - labCenter.z) / area.get(); + + int[] ds = new int[] {0, -1, 0, 1, 0}; + for (int is = 1; is < ds.length; is++) { + Point2i ptn = new Point2i(pti.x + ds[is - 1], pti.y + ds[is]); + int posn = ptn.x + ptn.y * sequence.getWidth(); + if (sequence.getBounds2D().contains(ptn.x, ptn.y) && !visited[posn] && clusters[posn] == val) { + visited[posn] = true; + q.add(ptn); + } + } + + } + } + + private List<Integer> findNeighbors(int[] newClusters, boolean[] visited, Point2i p) { + int posp = p.x + p.y * sequence.getWidth(); + + HashSet<Integer> neighs = new HashSet<>(); + + Deque<Point2i> q = new LinkedList<>(); + int val = newClusters[posp]; + + visited[posp] = false; + q.add(p); + + while (!q.isEmpty()) { + Point2i pti = q.pop(); + + int[] ds = new int[] {0, -1, 0, 1, 0}; + for (int is = 1; is < ds.length; is++) { + Point2i ptn = new Point2i(pti.x + ds[is - 1], pti.y + ds[is]); + int posn = ptn.x + ptn.y * sequence.getWidth(); + if (sequence.getBounds2D().contains(ptn.x, ptn.y)) { + if (newClusters[posn] == val) { + if (visited[posn]) { + visited[posn] = false; + q.add(ptn); + } + } else { + neighs.add(newClusters[posn]); + } + } + } + + } + return new ArrayList<Integer>(neighs); + } + + private double computeL(List<Point3d> labs, List<Double> areas, int i, Integer j) { + Vector3d diffLab = new Vector3d(); + diffLab.x = labs.get(j).x - labs.get(i).x; + diffLab.y = labs.get(j).y - labs.get(i).y; + diffLab.z = labs.get(j).z - labs.get(i).z; + + try { + return diffLab.length() / areas.get(j); + } catch (Exception e) { + throw e; + } + } + + private ROI2DArea defineROI(int[] newClusters, Point2i p, Point3d labP) { + int posp = p.x + p.y * sequence.getWidth(); + double[] lab = new double[] {labP.x, labP.y, labP.z}; + int[] rgb = CIELab.toRGB(lab); + + boolean[] bMask = new boolean[newClusters.length]; + int val = newClusters[posp]; + IntStream.range(0, newClusters.length).filter(pi -> newClusters[pi] == val).forEach(pi -> bMask[pi] = true); + + BooleanMask2D mask = new BooleanMask2D(sequence.getBounds2D(), bMask); + ROI2DArea roi = new ROI2DArea(mask); + roi.setColor(new Color(rgb[0], rgb[1], rgb[2])); + return roi; + } + + public Sequence getResultSequence() { + return this.superPixelsResult; + } +} diff --git a/SLIC/src/plugins/danyfel80/islic/LABToRGB.java b/SLIC/src/plugins/danyfel80/islic/LABToRGB.java new file mode 100644 index 0000000000000000000000000000000000000000..769ac462517a3909b874cee58d7323c5238a66ec --- /dev/null +++ b/SLIC/src/plugins/danyfel80/islic/LABToRGB.java @@ -0,0 +1,50 @@ +package plugins.danyfel80.islic; + +import java.util.stream.IntStream; + +import algorithms.danyfel80.islic.CIELab; +import icy.image.IcyBufferedImage; +import icy.sequence.Sequence; +import icy.type.DataType; +import icy.type.collection.array.Array2DUtil; +import plugins.adufour.ezplug.EzPlug; +import plugins.adufour.ezplug.EzVarSequence; + +public class LABToRGB extends EzPlug { + + EzVarSequence inLabSequence; + + @Override + protected void initialize() { + inLabSequence = new EzVarSequence("LAB sequence"); + addEzComponent(inLabSequence); + } + + @Override + protected void execute() { + Sequence labSequence = inLabSequence.getValue(); + Sequence rgbSequence = new Sequence(new IcyBufferedImage(labSequence.getWidth(), labSequence.getHeight(),3, DataType.UBYTE)); + + double[][] labIm = Array2DUtil.arrayToDoubleArray(labSequence.getDataXYC(0, 0), labSequence.isSignedDataType()); + + rgbSequence.beginUpdate(); + double[][] rgbIm = Array2DUtil.arrayToDoubleArray(rgbSequence.getDataXYC(0, 0), rgbSequence.isSignedDataType()); + IntStream.range(0, rgbIm[0].length).forEach(pos->{ + double[] lab = IntStream.range(0, 3).mapToDouble(c->labIm[c][pos]).toArray(); + int[] rgb = CIELab.toRGB(lab); + IntStream.range(0, 3).forEach(c->rgbIm[c][pos] = rgb[c]); + }); + Array2DUtil.doubleArrayToArray(rgbIm, rgbSequence.getDataXYC(0, 0)); + rgbSequence.dataChanged(); + rgbSequence.endUpdate(); + + + addSequence(rgbSequence); + } + + @Override + public void clean() { + // TODO Auto-generated method stub + } + +} diff --git a/SLIC/src/plugins/danyfel80/islic/RGBToLAB.java b/SLIC/src/plugins/danyfel80/islic/RGBToLAB.java new file mode 100644 index 0000000000000000000000000000000000000000..ac6139a254146b43809c93c8846c543b2c488d62 --- /dev/null +++ b/SLIC/src/plugins/danyfel80/islic/RGBToLAB.java @@ -0,0 +1,51 @@ +package plugins.danyfel80.islic; + +import java.util.stream.IntStream; + +import algorithms.danyfel80.islic.CIELab; +import icy.image.IcyBufferedImage; +import icy.sequence.Sequence; +import icy.type.DataType; +import icy.type.collection.array.Array2DUtil; +import plugins.adufour.ezplug.EzPlug; +import plugins.adufour.ezplug.EzVarSequence; + +public class RGBToLAB extends EzPlug { + + EzVarSequence inRgbSequence; + + @Override + protected void initialize() { + inRgbSequence = new EzVarSequence("RGB sequence"); + addEzComponent(inRgbSequence); + } + + @Override + protected void execute() { + + Sequence rgbSequence = inRgbSequence.getValue(); + Sequence labSequence = new Sequence( + new IcyBufferedImage(rgbSequence.getWidth(), rgbSequence.getHeight(), 3, DataType.DOUBLE)); + + double[][] rgbIm = Array2DUtil.arrayToDoubleArray(rgbSequence.getDataXYC(0, 0), rgbSequence.isSignedDataType()); + + labSequence.beginUpdate(); + double[][] labIm = Array2DUtil.arrayToDoubleArray(labSequence.getDataXYC(0, 0), labSequence.isSignedDataType()); + IntStream.range(0, labIm[0].length).forEach(pos -> { + int[] rgb = IntStream.range(0, 3).map(c -> (int) Math.round(255 * (rgbIm[c][pos] / rgbSequence.getDataTypeMax()))) + .toArray(); + double[] lab = CIELab.fromRGB(rgb); + IntStream.range(0, 3).forEach(c -> labIm[c][pos] = lab[c]); + }); + Array2DUtil.doubleArrayToArray(labIm, labSequence.getDataXYC(0, 0)); + labSequence.dataChanged(); + labSequence.endUpdate(); + + labSequence.setName(rgbSequence.getName() + "_LAB"); + addSequence(labSequence); + } + + @Override + public void clean() {} + +} diff --git a/SLIC/src/plugins/danyfel80/islic/SLIC.java b/SLIC/src/plugins/danyfel80/islic/SLIC.java new file mode 100644 index 0000000000000000000000000000000000000000..1f31909bfe710dea6f44aa2de83a91606b5c6698 --- /dev/null +++ b/SLIC/src/plugins/danyfel80/islic/SLIC.java @@ -0,0 +1,68 @@ +package plugins.danyfel80.islic; + +import algorithms.danyfel80.islic.SLICTask; +import icy.gui.dialog.MessageDialog; +import plugins.adufour.ezplug.EzPlug; +import plugins.adufour.ezplug.EzVarBoolean; +import plugins.adufour.ezplug.EzVarDouble; +import plugins.adufour.ezplug.EzVarInteger; +import plugins.adufour.ezplug.EzVarSequence; + +public class SLIC extends EzPlug { + + private EzVarSequence inSequence; + private EzVarInteger inSPSize; + private EzVarDouble inSPReg; + private EzVarBoolean inIsROIOutput; + + @Override + protected void initialize() { + inSequence = new EzVarSequence("Sequence"); + inSPSize = new EzVarInteger("Superpixel size"); + inSPReg = new EzVarDouble("Superpixels regularity"); + inIsROIOutput = new EzVarBoolean("Output as ROIs", false); + + inSPSize.setValue(30); + inSPReg.setValue(0.2); + + addEzComponent(inSequence); + addEzComponent(inSPSize); + addEzComponent(inSPReg); + addEzComponent(inIsROIOutput); + } + + @Override + protected void execute() { + long procTime, startTime, endTime; + SLICTask task; + try { + task = new SLICTask(inSequence.getValue(), inSPSize.getValue(), inSPReg.getValue(), inIsROIOutput.getValue()); + } catch (Exception e) { + e.printStackTrace(); + MessageDialog.showDialog("Initialization Error", + String.format("SLIC could not start properly: " + e.getMessage()), MessageDialog.ERROR_MESSAGE); + return; + } + startTime = System.currentTimeMillis(); + try { + task.execute(); + } catch (Exception e) { + e.printStackTrace(); + MessageDialog.showDialog("Runtime Error", String.format("SLIC could not run properly: " + e.getMessage()), + MessageDialog.ERROR_MESSAGE); + return; + } + + endTime = System.currentTimeMillis(); + procTime = endTime - startTime; + MessageDialog.showDialog(String.format("SLIC finished in %d milliseconds", procTime)); + System.out.println(String.format("SLIC finished in %d milliseconds", procTime)); + addSequence(task.getResultSequence()); + } + + @Override + public void clean() { + // TODO Auto-generated method stub + } + +}