diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..57f16fb67c1b1589981416b323d7a9debc728665 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +/build* +/workspace +setting.xml +release/ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +icy.log + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +**/.DS_Store +Icon? \ No newline at end of file diff --git a/SLIC/.gitignore b/SLIC/.gitignore deleted file mode 100644 index 95724b8f991472af8ffcd04eb6d8f3a824bc5a36..0000000000000000000000000000000000000000 --- a/SLIC/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/.gradle/ -/.settings/ -/bin/ -/build/ -/ecbuild/ -/target/ -/workspace/ -/.classpath -/.project -/setting.xml diff --git a/SLIC/pom.xml b/SLIC/pom.xml deleted file mode 100644 index 52f1eb106f5798b7677f289aee11154c91f56450..0000000000000000000000000000000000000000 --- a/SLIC/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>parent-pom-plugin</artifactId> - <version>1.0.4</version> - </parent> - <artifactId>slic</artifactId> - <version>1.0.1</version> - <name>SLIC - Superpixels</name> - <description/> - <build> - </build> - <dependencies> - <dependency> - <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>ezplug</artifactId> - </dependency> - <dependency> - <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>protocols</artifactId> - </dependency> - </dependencies> - <repositories> - <repository> - <id>icy</id> - <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> - </repository> - </repositories> -</project> \ No newline at end of file diff --git a/SLIC/src/main/java/algorithms/danyfel80/islic/CIELab.java b/SLIC/src/main/java/algorithms/danyfel80/islic/CIELab.java deleted file mode 100644 index 4c6a7e1b2ee462e290326bdab6337af65bd7e570..0000000000000000000000000000000000000000 --- a/SLIC/src/main/java/algorithms/danyfel80/islic/CIELab.java +++ /dev/null @@ -1,192 +0,0 @@ -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/main/java/algorithms/danyfel80/islic/SLICTask.java b/SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java deleted file mode 100644 index cf4420dde7819030de753251afda5a5c92c76a1a..0000000000000000000000000000000000000000 --- a/SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java +++ /dev/null @@ -1,629 +0,0 @@ -/* - * 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.awt.Point; -import java.awt.Rectangle; -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 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 icy.type.point.Point3D; -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<Point3D.Integer, double[]> colorLUT; - private Map<Point, 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) - { - - int[] rgbVal = new int[3]; - IntStream.range(0, 3).forEach(c -> { - rgbVal[c] = (int) Math - .round(255 * (sequenceData[c % sequence.getSizeC()][pos] / sequence.getDataTypeMax())); - }); - Point3D.Integer rgbPoint = new Point3D.Integer(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); - Point dPt = new Point((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.Double> labs = new ArrayList<>(SPnum); - List<Double> areas = new ArrayList<>(SPnum); - List<Point> firstPoints = new ArrayList<>(SPnum); - - // fill known clusters - AtomicInteger usedLabels = new AtomicInteger(0); - IntStream.range(0, SPnum).forEach(i -> { - Point3D.Double labCenter = new Point3D.Double(); - AtomicInteger area = new AtomicInteger(0); - Point p = new Point((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.Double labCenter = new Point3D.Double(); - AtomicInteger area = new AtomicInteger(0); - Point p = new Point(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.Double(), 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.setPixelSizeX(sequence.getPixelSizeX()); - superPixelsResult.setPixelSizeY(sequence.getPixelSizeY()); - superPixelsResult.setPixelSizeZ(sequence.getPixelSizeZ()); - superPixelsResult.setPositionX(sequence.getPositionX()); - superPixelsResult.setPositionY(sequence.getPositionY()); - superPixelsResult.setPositionZ(sequence.getPositionZ()); - - 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, - Point p, Point3D.Double labCenter, AtomicInteger area, int label) - { - int posp = p.x + p.y * sequence.getWidth(); - area.set(0); - labCenter.x = 0d; - labCenter.y = 0d; - labCenter.z = 0d; - - Deque<Point> q = new LinkedList<>(); - int val = clusters[posp]; - - visited[posp] = true; - q.add(p); - - while (!q.isEmpty()) - { - Point 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++) - { - Point ptn = new Point(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, Point p) - { - int posp = p.x + p.y * sequence.getWidth(); - - HashSet<Integer> neighs = new HashSet<>(); - - Deque<Point> q = new LinkedList<>(); - int val = newClusters[posp]; - - visited[posp] = false; - q.add(p); - - while (!q.isEmpty()) - { - Point pti = q.pop(); - - int[] ds = new int[] {0, -1, 0, 1, 0}; - for (int is = 1; is < ds.length; is++) - { - Point ptn = new Point(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.Double> labs, List<Double> areas, int i, Integer j) - { - Point3D.Double diffLab = new Point3D.Double(); - 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, Point p, Point3D.Double labP) - { - int posp = p.x + p.y * sequence.getWidth(); - double[] lab = new double[] {labP.x, labP.y, labP.z}; - int[] rgb = CIELab.toRGB(lab); - - int val = newClusters[posp]; - Rectangle seqBounds = sequence.getBounds2D(); - ROI2DArea roi1 = new ROI2DArea(new BooleanMask2D()); - IntStream.range(0, newClusters.length).filter(pi -> newClusters[pi] == val) - .forEach(pi -> roi1.addPoint(pi % seqBounds.width, pi / seqBounds.width));// .forEach(pi -> bMask[pi] = true); - - roi1.setColor(new Color(rgb[0], rgb[1], rgb[2])); - return roi1; - } - - public Sequence getResultSequence() - { - return this.superPixelsResult; - } -} diff --git a/SLIC/src/main/java/plugins/danyfel80/islic/LABToRGB.java b/SLIC/src/main/java/plugins/danyfel80/islic/LABToRGB.java deleted file mode 100644 index 769ac462517a3909b874cee58d7323c5238a66ec..0000000000000000000000000000000000000000 --- a/SLIC/src/main/java/plugins/danyfel80/islic/LABToRGB.java +++ /dev/null @@ -1,50 +0,0 @@ -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/main/java/plugins/danyfel80/islic/RGBToLAB.java b/SLIC/src/main/java/plugins/danyfel80/islic/RGBToLAB.java deleted file mode 100644 index ac6139a254146b43809c93c8846c543b2c488d62..0000000000000000000000000000000000000000 --- a/SLIC/src/main/java/plugins/danyfel80/islic/RGBToLAB.java +++ /dev/null @@ -1,51 +0,0 @@ -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/main/java/plugins/danyfel80/islic/SLIC.java b/SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java deleted file mode 100644 index ea1dc51dd027f4865f799ac472768effd2621e9f..0000000000000000000000000000000000000000 --- a/SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java +++ /dev/null @@ -1,135 +0,0 @@ -package plugins.danyfel80.islic; - -import algorithms.danyfel80.islic.SLICTask; -import icy.gui.dialog.MessageDialog; -import icy.main.Icy; -import icy.plugin.PluginLauncher; -import icy.plugin.PluginLoader; -import plugins.adufour.blocks.lang.Block; -import plugins.adufour.blocks.util.VarList; -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 implements Block -{ - - private EzVarSequence inSequence; - private EzVarInteger inSPSize; - private EzVarDouble inSPReg; - private EzVarBoolean inIsROIOutput; - - private EzVarSequence outSequence; - - @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 - public void declareInput(VarList inputMap) - { - 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); - - inputMap.add(inSequence.name, inSequence.getVariable()); - inputMap.add(inSPSize.name, inSPSize.getVariable()); - inputMap.add(inSPReg.name, inSPReg.getVariable()); - inputMap.add(inIsROIOutput.name, inIsROIOutput.getVariable()); - } - - @Override - public void declareOutput(VarList outputMap) - { - outSequence = new EzVarSequence("Result"); - - outputMap.add(outSequence.name, outSequence.getVariable()); - } - - @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(); - if (!this.isHeadLess()) - { - 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(); - if (!this.isHeadLess()) - { - MessageDialog.showDialog("Runtime Error", - String.format("SLIC could not run properly: " + e.getMessage()), MessageDialog.ERROR_MESSAGE); - } - return; - } - - endTime = System.currentTimeMillis(); - procTime = endTime - startTime; - System.out.println(String.format("SLIC finished in %d milliseconds", procTime)); - if (!this.inIsROIOutput.getValue()) - { - task.getResultSequence().setName(inSequence.getValue().getName() + String.format("_SLIC(size=%s,reg=%.2f)", - inSPSize.getValue().intValue(), inSPReg.getValue().doubleValue())); - } - if (!this.isHeadLess()) - { - MessageDialog.showDialog(String.format("SLIC finished in %d milliseconds", procTime)); - addSequence(task.getResultSequence()); - } - else - { - outSequence.setValue(task.getResultSequence()); - } - - } - - @Override - public void clean() - { - // TODO Auto-generated method stub - } - - public static void main(String[] args) - { - Icy.main(args); - PluginLauncher.start(PluginLoader.getPlugin(SLIC.class.getName())); - } -} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..324627dedb8a8ae6dd7575a41aaf08861b369bc0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>pom-icy</artifactId> + <version>3.0.0-a.1</version> + </parent> + + <artifactId>slic</artifactId> + <version>2.0.0-a.1</version> + + <name>SLIC - Superpixels</name> + + <dependencies> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>ezplug</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>protocols</artifactId> + </dependency> + </dependencies> + + <repositories> + <repository> + <id>icy</id> + <url>https://nexus-icy.pasteur.cloud/repository/icy/</url> + </repository> + </repositories> +</project> \ No newline at end of file diff --git a/src/main/java/algorithms/danyfel80/islic/CIELab.java b/src/main/java/algorithms/danyfel80/islic/CIELab.java new file mode 100644 index 0000000000000000000000000000000000000000..020c402a41aadcf9b8bec17ea21076bc3e00b5c1 --- /dev/null +++ b/src/main/java/algorithms/danyfel80/islic/CIELab.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2010-2024. 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 <https://www.gnu.org/licenses/>. + */ + +package algorithms.danyfel80.islic; + +import org.jetbrains.annotations.NotNull; + +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 @NotNull [] fromRGB(final int @NotNull [] rgb) { + return fromRGB(rgb[0], rgb[1], rgb[2]); + } + + public static double @NotNull [] fromRGB(final int R, final int G, final int B) { + final 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 @NotNull [] toRGB(final double @NotNull [] xyz) { + return toRGB(xyz[0], xyz[1], xyz[2]); + } + + public static int @NotNull [] toRGB(final double X, final double Y, final double Z) { + final int[] result = new int[3]; + + final double x = X / 100.0; + final double y = Y / 100.0; + final 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 @NotNull [] fromRGB(final int[] rgb) { + return fromXYZ(XYZ.fromRGB(rgb)); + } + + public static double @NotNull [] fromRGB(final int r, final int g, final int b) { + return fromXYZ(XYZ.fromRGB(r, g, b)); + } + + private static double @NotNull [] fromXYZ(final double @NotNull [] xyz) { + return fromXYZ(xyz[0], xyz[1], xyz[2]); + } + + public static double @NotNull [] fromXYZ(final double X, final double Y, final 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); + } + + final 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 @NotNull [] toRGB(final double[] lab) { + return XYZ.toRGB(toXYZ(lab)); + } + + public static int @NotNull [] toRGB(final double L, final double a, final double b) { + return XYZ.toRGB(toXYZ(L, a, b)); + } + + public static double @NotNull [] toXYZ(final double @NotNull [] lab) { + return toXYZ(lab[0], lab[1], lab[2]); + } + + public static double @NotNull [] toXYZ(final double L, final double a, final double b) { + final double[] result = new double[3]; + + double y = (L + 16.0) / 116.0; + final double y3 = Math.pow(y, 3.0); + double x = (a / 500.0) + y; + final double x3 = Math.pow(x, 3.0); + double z = y - (b / 200.0); + final 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/src/main/java/algorithms/danyfel80/islic/SLICTask.java b/src/main/java/algorithms/danyfel80/islic/SLICTask.java new file mode 100644 index 0000000000000000000000000000000000000000..254b5080799008731962d14991a46b7ed0f755bb --- /dev/null +++ b/src/main/java/algorithms/danyfel80/islic/SLICTask.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2010-2024. 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 <https://www.gnu.org/licenses/>. + */ + +package algorithms.danyfel80.islic; + +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DArea; +import org.bioimageanalysis.icy.common.collection.array.Array2DUtil; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.type.DataType; +import org.bioimageanalysis.icy.model.image.IcyBufferedImage; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.mask.BooleanMask2D; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +/** + * @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 final Sequence sequence; + private final int S; + private final int Sx; + private final int Sy; + private final int SPnum; + private final double rigidity; + + private final boolean computeROIs; + private List<ROI> rois; + private Sequence superPixelsResult; + + private final Map<Point3D.Integer, double[]> colorLUT; + private final Map<Point, Double> distanceLUT; + + public SLICTask(@NotNull final Sequence sequence, final int SPSize, final double rigidity, final 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() { + final double[] ls = new double[SPnum]; + final double[] as = new double[SPnum]; + final double[] bs = new double[SPnum]; + + final double[] cxs = new double[SPnum]; + final double[] cys = new double[SPnum]; + + final double[][] sequenceData = Array2DUtil.arrayToDoubleArray( + sequence.getDataXYC(0, 0), + sequence.isSignedDataType() + ); + + initialize(ls, as, bs, cxs, cys, sequenceData); + + final int[] clusters = new int[sequence.getWidth() * sequence.getHeight()]; + final 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++; + IcyLogger.trace(this.getClass(), String.format("Iteration=%d, Error=%f", iterations, error)); + } + while (error > ERROR_THRESHOLD && iterations < MAX_ITERATIONS); + IcyLogger.trace(this.getClass(), "End of iterations%n"); + + computeSuperpixels(sequenceData, clusters, ls, as, bs, cxs, cys); + } + + private void initialize(final double[] ls, final double[] as, final double[] bs, final double[] cxs, final double[] cys, final double[][] sequenceData) { + IntStream.range(0, SPnum).forEach(sp -> { + final int x; + final int y; + final int yOff; + int bestx; + int besty; + int bestyOff; + int i; + int j; + int yjOff; + double bestGradient, candidateGradient; + + final int spx = sp % Sx; + final 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; + final int bestPos = bestx + bestyOff; + + final double[] bestLAB = getCIELab(sequenceData, bestPos); + ls[sp] = bestLAB[0]; + as[sp] = bestLAB[1]; + bs[sp] = bestLAB[2]; + }); + } + + private double getGradient(final double[][] data, final int pos1, final int pos2) { + final int[] valRGB1 = new int[3]; + final 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 (final Exception e) { + IcyLogger.error(this.getClass(), e, "Error while getting gradient."); + throw e; + } + }); + + final double[] val1 = CIELab.fromRGB(valRGB1); + final double[] val2 = CIELab.fromRGB(valRGB2); + + return IntStream.range(0, 3).mapToDouble(c -> val2[c] - val1[c]).average().orElseThrow(); + } + + private double @NotNull [] getCIELab(final double[][] sequenceData, final int pos) { + final int[] rgbVal = new int[3]; + IntStream.range(0, 3).forEach(c -> rgbVal[c] = (int) Math + .round(255 * (sequenceData[c % sequence.getSizeC()][pos] / sequence.getDataTypeMax()))); + final Point3D.Integer rgbPoint = new Point3D.Integer(rgbVal); + + double[] LAB = colorLUT.get(rgbPoint); + if (LAB == null) { + final 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( + final double[][] sequenceData, + final int[] clusters, + final double[] distances, + final double[] ls, + final double[] as, + final double[] bs, + final double[] cxs, + final double[] cys + ) { + // Each cluster + IntStream.range(0, SPnum).forEach(k -> { + final double ckx = cxs[k]; + final double cky = cys[k]; + final double lk = ls[k]; + final double ak = as[k]; + final double bk = bs[k]; + final 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++) { + final int ciy = (int) (cky + j); + if (ciy >= 0 && ciy < sequence.getHeight()) { + yOff = ciy * sequence.getWidth(); + for (i = -S; i < S; i++) { + final int cix = (int) (ckx + i); + if (cix >= 0 && cix < sequence.getWidth()) { + final int posi = cix + yOff; + final double[] LABi = getCIELab(sequenceData, posi); + final double li = LABi[0]; + final double ai = LABi[1]; + final double bi = LABi[2]; + + final 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( + final double ckx, + final double cky, + final double lk, + final double ak, + final double bk, + final int dx, + final int dy, + final double li, + final double ai, + final double bi + ) { + final double diffl; + final double diffa; + final double diffb; + diffl = li - lk; + diffa = ai - ak; + diffb = bi - bk; + + final double dc = Math.sqrt(diffl * diffl + diffa * diffa + diffb * diffb); + final Point dPt = new Point(Math.min(dx, dy), Math.max(dx, dy)); + final Double ds = distanceLUT.computeIfAbsent(dPt, k -> Math.sqrt(dx * dx + dy * dy)); + return Math.sqrt(dc * dc + (ds * ds) * (rigidity * rigidity * S)); + } + + private double updateClusters( + final double[][] sequenceData, + final int[] clusters, + final double[] ls, + final double[] as, + final double[] bs, + final double[] cxs, + final double[] cys + ) { + final double[] newls = new double[SPnum]; + final double[] newas = new double[SPnum]; + final double[] newbs = new double[SPnum]; + final double[] newcxs = new double[SPnum]; + final double[] newcys = new double[SPnum]; + final 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; + final int sp = clusters[pos]; + final 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]; + } + } + + return IntStream.range(0, SPnum).mapToDouble(sp -> { + // double diffl = ls[sp] - newls[sp]; + // double diffa = as[sp] - newas[sp]; + // double diffb = bs[sp] - newbs[sp]; + final double diffx = cxs[sp] - newcxs[sp]; + final 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; + } + + private void computeSuperpixels( + final double[][] sequenceData, + final int @NotNull [] clusters, + final double[] ls, + final double[] as, + final double[] bs, + final double[] cxs, + final double[] cys + ) { + final boolean[] visited = new boolean[clusters.length]; + final int[] finalClusters = new int[clusters.length]; + + final List<Point3D.Double> labs = new ArrayList<>(SPnum); + final List<Double> areas = new ArrayList<>(SPnum); + final List<Point> firstPoints = new ArrayList<>(SPnum); + + // fill known clusters + final AtomicInteger usedLabels = new AtomicInteger(0); + IntStream.range(0, SPnum).forEach(i -> { + final Point3D.Double labCenter = new Point3D.Double(); + final AtomicInteger area = new AtomicInteger(0); + final Point p = new Point((int) Math.round(cxs[i]), (int) Math.round(cys[i])); + final 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); + } + }); + + final int firstUnknownCluster = usedLabels.get(); + + // fill independent clusters + for (int y = 0; y < sequence.getHeight(); y++) { + final int yOff = y * sequence.getWidth(); + for (int x = 0; x < sequence.getWidth(); x++) { + final int pos = x + yOff; + if (!visited[pos]) { + final Point3D.Double labCenter = new Point3D.Double(); + final AtomicInteger area = new AtomicInteger(0); + final Point p = new Point(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 + final boolean[] mergedClusters = new boolean[usedLabels.get()]; + final int[] mergedRefs = IntStream.range(0, usedLabels.get()).toArray(); + IntStream.range(firstUnknownCluster, usedLabels.get()).forEach(i -> { + final List<Integer> neighbors = findNeighbors(finalClusters, visited, firstPoints.get(i)); + if (!neighbors.isEmpty()) { + int bestNeighbour = neighbors.getFirst(); + double bestL = Double.MAX_VALUE; + + // boolean found = false; + for (final Integer j : neighbors) { + if (j < i) { + // found = true; + final 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) { + */ + final double rArea = areas.get(i); + double relSPSize = rArea / 4; + relSPSize *= relSPSize; + + final double coeff = relSPSize * (1.0 + bestL); + if (coeff < EPSILON) { + mergedClusters[i] = true; + mergedRefs[i] = bestNeighbour; + } + // } else { + // mergedClusters[i] = true; + // mergedRefs[i] = bestNeighbour; + // } + } + else { + IcyLogger.error( + this.getClass(), + String.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.Double(), 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]) { + final ROI2DArea roi = defineROI(finalClusters, firstPoints.get(i), labs.get(i)); + rois.add(roi); + } + }); + sequence.addROIs(rois, false); + } + else { + final List<int[]> rgbs = labs.stream().map(lab -> CIELab.toRGB(lab.x, lab.y, lab.z)).toList(); + superPixelsResult = new Sequence(new IcyBufferedImage(sequence.getWidth(), sequence.getHeight(), 3, DataType.UBYTE)); + + superPixelsResult.setPixelSizeX(sequence.getPixelSizeX()); + superPixelsResult.setPixelSizeY(sequence.getPixelSizeY()); + superPixelsResult.setPixelSizeZ(sequence.getPixelSizeZ()); + superPixelsResult.setPositionX(sequence.getPositionX()); + superPixelsResult.setPositionY(sequence.getPositionY()); + superPixelsResult.setPositionZ(sequence.getPositionZ()); + + superPixelsResult.beginUpdate(); + final 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; + final 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( + final double[][] sequenceData, + final int @NotNull [] clusters, + final int[] newClusters, + final boolean @NotNull [] visited, + @NotNull final Point p, + final Point3D.@NotNull Double labCenter, + @NotNull final AtomicInteger area, + final int label + ) { + final int posp = p.x + p.y * sequence.getWidth(); + area.set(0); + labCenter.x = 0d; + labCenter.y = 0d; + labCenter.z = 0d; + + final Deque<Point> q = new LinkedList<>(); + final int val = clusters[posp]; + + visited[posp] = true; + q.add(p); + + while (!q.isEmpty()) { + final Point pti = q.pop(); + final int posi = pti.x + pti.y * sequence.getWidth(); + + newClusters[posi] = label; + area.getAndIncrement(); + final 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(); + + final int[] ds = new int[]{0, -1, 0, 1, 0}; + for (int is = 1; is < ds.length; is++) { + final Point ptn = new Point(pti.x + ds[is - 1], pti.y + ds[is]); + final 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); + } + } + + } + } + + @Contract("_, _, _ -> new") + private @NotNull List<Integer> findNeighbors(final int @NotNull [] newClusters, final boolean @NotNull [] visited, @NotNull final Point p) { + final int posp = p.x + p.y * sequence.getWidth(); + + final HashSet<Integer> neighs = new HashSet<>(); + + final Deque<Point> q = new LinkedList<>(); + final int val = newClusters[posp]; + + visited[posp] = false; + q.add(p); + + while (!q.isEmpty()) { + final Point pti = q.pop(); + + final int[] ds = new int[]{0, -1, 0, 1, 0}; + for (int is = 1; is < ds.length; is++) { + final Point ptn = new Point(pti.x + ds[is - 1], pti.y + ds[is]); + final 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<>(neighs); + } + + private double computeL(@NotNull final List<Point3D.Double> labs, final List<Double> areas, final int i, final Integer j) { + final Point3D.Double diffLab = new Point3D.Double(); + 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 (final Exception e) { + IcyLogger.error(this.getClass(), e); + throw e; + } + } + + private @NotNull ROI2DArea defineROI(final int @NotNull [] newClusters, @NotNull final Point p, final Point3D.@NotNull Double labP) { + final int posp = p.x + p.y * sequence.getWidth(); + final double[] lab = new double[]{labP.x, labP.y, labP.z}; + final int[] rgb = CIELab.toRGB(lab); + + final int val = newClusters[posp]; + final Rectangle seqBounds = sequence.getBounds2D(); + final ROI2DArea roi1 = new ROI2DArea(new BooleanMask2D()); + IntStream.range(0, newClusters.length) + .filter(pi -> newClusters[pi] == val) + .forEach(pi -> roi1.addPoint(pi % seqBounds.width, pi / seqBounds.width));// .forEach(pi -> bMask[pi] = true); + + roi1.setColor(new Color(rgb[0], rgb[1], rgb[2])); + return roi1; + } + + public Sequence getResultSequence() { + return this.superPixelsResult; + } +} diff --git a/src/main/java/plugins/danyfel80/islic/LABToRGB.java b/src/main/java/plugins/danyfel80/islic/LABToRGB.java new file mode 100644 index 0000000000000000000000000000000000000000..48ebd3d72c524eaf7b3ff3c43af9b4809b4145dd --- /dev/null +++ b/src/main/java/plugins/danyfel80/islic/LABToRGB.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010-2024. 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 <https://www.gnu.org/licenses/>. + */ + +package plugins.danyfel80.islic; + +import algorithms.danyfel80.islic.CIELab; +import org.bioimageanalysis.icy.common.collection.array.Array2DUtil; +import org.bioimageanalysis.icy.common.type.DataType; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.model.image.IcyBufferedImage; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import plugins.adufour.ezplug.EzPlug; +import plugins.adufour.ezplug.EzVarSequence; + +import java.util.stream.IntStream; + +@IcyPluginName("LAB to RGB") +@IcyPluginIcon(path = "/slic.png") +public class LABToRGB extends EzPlug { + EzVarSequence inLabSequence; + + @Override + protected void initialize() { + inLabSequence = new EzVarSequence("LAB sequence"); + addEzComponent(inLabSequence); + } + + @Override + protected void execute() { + final Sequence labSequence = inLabSequence.getValue(); + final Sequence rgbSequence = new Sequence(new IcyBufferedImage(labSequence.getWidth(), + labSequence.getHeight(), + 3, + DataType.UBYTE + )); + + final double[][] labIm = Array2DUtil.arrayToDoubleArray(labSequence.getDataXYC(0, 0), labSequence.isSignedDataType()); + + rgbSequence.beginUpdate(); + final double[][] rgbIm = Array2DUtil.arrayToDoubleArray(rgbSequence.getDataXYC(0, 0), rgbSequence.isSignedDataType()); + IntStream.range(0, rgbIm[0].length).forEach(pos -> { + final double[] lab = IntStream.range(0, 3).mapToDouble(c -> labIm[c][pos]).toArray(); + final 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() { + // + } +} diff --git a/src/main/java/plugins/danyfel80/islic/RGBToLAB.java b/src/main/java/plugins/danyfel80/islic/RGBToLAB.java new file mode 100644 index 0000000000000000000000000000000000000000..f9ceee5b8bc2b8906fb8c1556302914815405b8e --- /dev/null +++ b/src/main/java/plugins/danyfel80/islic/RGBToLAB.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010-2024. 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 <https://www.gnu.org/licenses/>. + */ + +package plugins.danyfel80.islic; + +import algorithms.danyfel80.islic.CIELab; +import org.bioimageanalysis.icy.common.collection.array.Array2DUtil; +import org.bioimageanalysis.icy.common.type.DataType; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.model.image.IcyBufferedImage; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import plugins.adufour.ezplug.EzPlug; +import plugins.adufour.ezplug.EzVarSequence; + +import java.util.stream.IntStream; + +@IcyPluginName("RGB to LAB") +@IcyPluginIcon(path = "/slic.png") +public class RGBToLAB extends EzPlug { + EzVarSequence inRgbSequence; + + @Override + protected void initialize() { + inRgbSequence = new EzVarSequence("RGB sequence"); + addEzComponent(inRgbSequence); + } + + @Override + protected void execute() { + final Sequence rgbSequence = inRgbSequence.getValue(); + final Sequence labSequence = new Sequence(new IcyBufferedImage( + rgbSequence.getWidth(), + rgbSequence.getHeight(), + 3, + DataType.DOUBLE + )); + + final double[][] rgbIm = Array2DUtil.arrayToDoubleArray(rgbSequence.getDataXYC(0, 0), rgbSequence.isSignedDataType()); + + labSequence.beginUpdate(); + final double[][] labIm = Array2DUtil.arrayToDoubleArray(labSequence.getDataXYC(0, 0), labSequence.isSignedDataType()); + IntStream.range(0, labIm[0].length).forEach(pos -> { + final int[] rgb = IntStream.range(0, 3).map(c -> (int) Math.round(255 * (rgbIm[c][pos] / rgbSequence.getDataTypeMax()))).toArray(); + final 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/src/main/java/plugins/danyfel80/islic/SLIC.java b/src/main/java/plugins/danyfel80/islic/SLIC.java new file mode 100644 index 0000000000000000000000000000000000000000..9bc58bd05d88e3d058037d07035d1ea3994c0424 --- /dev/null +++ b/src/main/java/plugins/danyfel80/islic/SLIC.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2010-2024. 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 <https://www.gnu.org/licenses/>. + */ + +package plugins.danyfel80.islic; + +import algorithms.danyfel80.islic.SLICTask; +import org.bioimageanalysis.icy.Icy; +import org.bioimageanalysis.icy.extension.plugin.PluginLauncher; +import org.bioimageanalysis.icy.extension.plugin.PluginLoader; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.jetbrains.annotations.NotNull; +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.ezplug.*; + +@IcyPluginName("SLIC") +@IcyPluginIcon(path = "/slic.png") +public class SLIC extends EzPlug implements Block { + private EzVarSequence inSequence; + private EzVarInteger inSPSize; + private EzVarDouble inSPReg; + private EzVarBoolean inIsROIOutput; + + private EzVarSequence outSequence; + + @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 + public void declareInput(final @NotNull VarList inputMap) { + 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); + + inputMap.add(inSequence.name, inSequence.getVariable()); + inputMap.add(inSPSize.name, inSPSize.getVariable()); + inputMap.add(inSPReg.name, inSPReg.getVariable()); + inputMap.add(inIsROIOutput.name, inIsROIOutput.getVariable()); + } + + @Override + public void declareOutput(final @NotNull VarList outputMap) { + outSequence = new EzVarSequence("Result"); + + outputMap.add(outSequence.name, outSequence.getVariable()); + } + + @Override + protected void execute() { + final long procTime; + final long startTime; + final long endTime; + final SLICTask task; + try { + task = new SLICTask(inSequence.getValue(), inSPSize.getValue(), inSPReg.getValue(), + inIsROIOutput.getValue()); + } + catch (final Exception e) { + IcyLogger.error(this.getClass(), e, "SLIC could not start properly."); + return; + } + startTime = System.currentTimeMillis(); + try { + task.execute(); + } + catch (final Exception e) { + IcyLogger.error(this.getClass(), e, "SLIC could not run properly."); + return; + } + + endTime = System.currentTimeMillis(); + procTime = endTime - startTime; + IcyLogger.info(this.getClass(), String.format("SLIC finished in %d milliseconds", procTime)); + if (!this.inIsROIOutput.getValue()) { + task.getResultSequence().setName(inSequence.getValue().getName() + String.format( + "_SLIC(size=%s,reg=%.2f)", + inSPSize.getValue(), + inSPReg.getValue()) + ); + } + if (!this.isHeadLess()) { + addSequence(task.getResultSequence()); + } + else { + outSequence.setValue(task.getResultSequence()); + } + + } + + @Override + public void clean() { + // + } + + @SuppressWarnings("resource") + public static void main(final String[] args) { + Icy.main(args); + PluginLauncher.start(PluginLoader.getPlugin(SLIC.class.getName())); + } +} diff --git a/src/main/resources/slic.png b/src/main/resources/slic.png new file mode 100644 index 0000000000000000000000000000000000000000..99e74d99539e812d1a3d6c21386d66f2f83d194a Binary files /dev/null and b/src/main/resources/slic.png differ