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