From 2df9740b75a686724dcf5c3683743fc86646efb8 Mon Sep 17 00:00:00 2001
From: Thomas <thomas.musset@pasteur.fr>
Date: Sun, 7 Jul 2024 00:38:32 +0200
Subject: [PATCH] updated pom to v2.0.0-a.1, fix classes accordingly to new
 architecture, added icon, updated .gitignore

---
 .gitignore                                    |  41 ++
 SLIC/.gitignore                               |  10 -
 SLIC/pom.xml                                  |  31 -
 .../algorithms/danyfel80/islic/CIELab.java    | 192 ------
 .../algorithms/danyfel80/islic/SLICTask.java  | 629 ------------------
 .../plugins/danyfel80/islic/LABToRGB.java     |  50 --
 .../plugins/danyfel80/islic/RGBToLAB.java     |  51 --
 .../java/plugins/danyfel80/islic/SLIC.java    | 135 ----
 pom.xml                                       |  35 +
 .../algorithms/danyfel80/islic/CIELab.java    | 225 +++++++
 .../algorithms/danyfel80/islic/SLICTask.java  | 601 +++++++++++++++++
 .../plugins/danyfel80/islic/LABToRGB.java     |  74 +++
 .../plugins/danyfel80/islic/RGBToLAB.java     |  75 +++
 .../java/plugins/danyfel80/islic/SLIC.java    | 134 ++++
 src/main/resources/slic.png                   | Bin 0 -> 9879 bytes
 15 files changed, 1185 insertions(+), 1098 deletions(-)
 create mode 100644 .gitignore
 delete mode 100644 SLIC/.gitignore
 delete mode 100644 SLIC/pom.xml
 delete mode 100644 SLIC/src/main/java/algorithms/danyfel80/islic/CIELab.java
 delete mode 100644 SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java
 delete mode 100644 SLIC/src/main/java/plugins/danyfel80/islic/LABToRGB.java
 delete mode 100644 SLIC/src/main/java/plugins/danyfel80/islic/RGBToLAB.java
 delete mode 100644 SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java
 create mode 100644 pom.xml
 create mode 100644 src/main/java/algorithms/danyfel80/islic/CIELab.java
 create mode 100644 src/main/java/algorithms/danyfel80/islic/SLICTask.java
 create mode 100644 src/main/java/plugins/danyfel80/islic/LABToRGB.java
 create mode 100644 src/main/java/plugins/danyfel80/islic/RGBToLAB.java
 create mode 100644 src/main/java/plugins/danyfel80/islic/SLIC.java
 create mode 100644 src/main/resources/slic.png

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..57f16fb
--- /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 95724b8..0000000
--- 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 52f1eb1..0000000
--- 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 4c6a7e1..0000000
--- 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 cf4420d..0000000
--- 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 769ac46..0000000
--- 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 ac6139a..0000000
--- 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 ea1dc51..0000000
--- 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 0000000..324627d
--- /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 0000000..020c402
--- /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 0000000..254b508
--- /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 0000000..48ebd3d
--- /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 0000000..f9ceee5
--- /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 0000000..9bc58bd
--- /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
GIT binary patch
literal 9879
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
aj_9ZpYh29V@jg7OA8M-`sMV<0hW;O3JsQ;j

literal 0
HcmV?d00001

-- 
GitLab