diff --git a/SLIC/.gitignore b/SLIC/.gitignore
index 34ef6b1bc2942265008040c686460cf675dc44a2..1522ef951c7d719126cbb5504e128e2f71a7ac19 100644
--- a/SLIC/.gitignore
+++ b/SLIC/.gitignore
@@ -5,3 +5,4 @@
 /.project
 /build/
 /ecbuild/
+/target/
diff --git a/SLIC/build.gradle b/SLIC/build.gradle
deleted file mode 100644
index 61de65f3ea41ad95b6efb01c56b48088769ff758..0000000000000000000000000000000000000000
--- a/SLIC/build.gradle
+++ /dev/null
@@ -1,52 +0,0 @@
-apply plugin: 'java-library'
-apply plugin: 'eclipse'
-
-version = '0.1.0'
-
-println "ICY_HOME=${System.env.ICY_HOME}"
-println project.name + " " + version
-
-configurations {
-  extraLibs // configuration that holds jars to include in the jar
-  implementation.extendsFrom(extraLibs)
-}
-
-repositories {
-  mavenCentral()
-  jcenter()
-}
-
-dependencies {
-  extraLibs 'org.slf4j:slf4j-api:1.7.21'
-
-  implementation files("${System.env.ICY_HOME}/icy.jar") // Icy core
-  implementation files("${System.env.ICY_HOME}/lib/bioformats.jar") // bioformats
-  implementation files("${System.env.ICY_HOME}/plugins/adufour/ezplug/EzPlug.jar") // EzPlug
-  implementation files("${System.env.ICY_HOME}/plugins/adufour/blocks/Blocks.jar") // Blocks
-  implementation files("${System.env.ICY_HOME}/plugins/adufour/protocols/Protocols.jar") // Protocols
-
-  testImplementation 'junit:junit:4.12'
-}
-
-// In this section we add the icy nature of the project in eclipse
-eclipse {
-  classpath.downloadJavadoc = true
-  project.natures += ['icy.icy4eclipse.core.icynature']
-}
-
-task sourcesJar(type: Jar) {
-  classifier = 'sources'
-  from sourceSets.main.output
-  from sourceSets.main.java
-  from configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) }
-}
-
-task javadocJar(type: Jar, dependsOn: javadoc) {
-  classifier = 'javadoc'
-  from javadoc.destinationDir
-}
-
-artifacts {
-  archives sourcesJar
-  archives javadocJar
-}
\ No newline at end of file
diff --git a/SLIC/gradle/wrapper/gradle-wrapper.jar b/SLIC/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..0000000000000000000000000000000000000000
Binary files a/SLIC/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/SLIC/gradle/wrapper/gradle-wrapper.properties b/SLIC/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index b17c75f10d9ef7fa22219bdf3ac40849d84702da..0000000000000000000000000000000000000000
--- a/SLIC/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/SLIC/gradlew b/SLIC/gradlew
deleted file mode 100644
index 83f2acfdc319a24e8766cca78f32474ad7a22dd6..0000000000000000000000000000000000000000
--- a/SLIC/gradlew
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
-    echo "$*"
-}
-
-die () {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-  NONSTOP* )
-    nonstop=true
-    ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Escape application args
-save () {
-    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
-    echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
-  cd "$(dirname "$0")"
-fi
-
-exec "$JAVACMD" "$@"
diff --git a/SLIC/gradlew.bat b/SLIC/gradlew.bat
deleted file mode 100644
index 9618d8d9607cd91a0efb866bcac4810064ba6fac..0000000000000000000000000000000000000000
--- a/SLIC/gradlew.bat
+++ /dev/null
@@ -1,100 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/SLIC/pom.xml b/SLIC/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..52f1eb106f5798b7677f289aee11154c91f56450
--- /dev/null
+++ b/SLIC/pom.xml
@@ -0,0 +1,31 @@
+<?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/SLICTask.java b/SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java
index 44c705c3ed43706c98bf0ead4e53150b8e5a873c..cf4420dde7819030de753251afda5a5c92c76a1a 100644
--- a/SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java
+++ b/SLIC/src/main/java/algorithms/danyfel80/islic/SLICTask.java
@@ -20,6 +20,7 @@ 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;
@@ -44,517 +45,585 @@ 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);
-
-		boolean[] bMask = new boolean[newClusters.length];
-		int val = newClusters[posp];
-		IntStream.range(0, newClusters.length).filter(pi -> newClusters[pi] == val).forEach(pi -> bMask[pi] = true);
-
-		BooleanMask2D mask = new BooleanMask2D(sequence.getBounds2D(), bMask);
-		ROI2DArea roi = new ROI2DArea(mask);
-		roi.setColor(new Color(rgb[0], rgb[1], rgb[2]));
-		return roi;
-	}
-
-	public Sequence getResultSequence() {
-		return this.superPixelsResult;
-	}
+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/SLIC.java b/SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java
index 1c467d266c7be94474adba4fc68fd9fc55affa14..ea1dc51dd027f4865f799ac472768effd2621e9f 100644
--- a/SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java
+++ b/SLIC/src/main/java/plugins/danyfel80/islic/SLIC.java
@@ -2,6 +2,9 @@ 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;
@@ -10,99 +13,123 @@ 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 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()));
+    }
 }