diff --git a/pom.xml b/pom.xml
index be90d6a840c71976966cc8f5f039a5f0b6d77394..e0ec4fa76036db71d44ab84517a5038dcf06c2fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
     </parent>
 
     <artifactId>label-extractor</artifactId>
-    <version>2.0.0-a.2</version>
+    <version>2.0.0-a.3</version>
 
     <name>Label extractor</name>
     <description>
diff --git a/src/main/java/plugins/adufour/roi/LabelExtractor.java b/src/main/java/plugins/adufour/roi/LabelExtractor.java
index f6cda0082962a865f8a9a205cd7f97577d215dac..81b98f0aa92325fadf56a6a933345160a704dbb5 100644
--- a/src/main/java/plugins/adufour/roi/LabelExtractor.java
+++ b/src/main/java/plugins/adufour/roi/LabelExtractor.java
@@ -46,7 +46,7 @@ import java.util.Map;
  */
 @IcyPluginName("Label Extractor")
 @IcyPluginIcon(path = "/label-extractor.png")
-public class LabelExtractor extends EzPlug implements Block, EzStoppable {
+public class LabelExtractor extends EzPlug implements EzStoppable {
     EzVarSequence inSeq = new EzVarSequence("Labeled sequence");
 
     EzVarEnum<ExtractionType> type = new EzVarEnum<>("Extract", ExtractionType.values());
@@ -85,18 +85,6 @@ public class LabelExtractor extends EzPlug implements Block, EzStoppable {
         }
     }
 
-    @Override
-    public void declareInput(final VarList inputMap) {
-        inputMap.add("input sequence", inSeq.getVariable());
-        inputMap.add("extract mode", type.getVariable());
-        inputMap.add("value", value.getVariable());
-    }
-
-    @Override
-    public void declareOutput(final VarList outputMap) {
-        outputMap.add("ROI", outROI);
-    }
-
     @Override
     public void clean() {
 
diff --git a/src/main/java/plugins/adufour/roi/LabelExtractorBlock.java b/src/main/java/plugins/adufour/roi/LabelExtractorBlock.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3ed676d9df474d77bea27c31bf8afa7b4c87c0d
--- /dev/null
+++ b/src/main/java/plugins/adufour/roi/LabelExtractorBlock.java
@@ -0,0 +1,674 @@
+/*
+ * 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.adufour.roi;
+
+import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DArea;
+import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DArea;
+import org.bioimageanalysis.icy.common.collection.array.Array1DUtil;
+import org.bioimageanalysis.icy.common.type.DataType;
+import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin;
+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.roi.ROI;
+import org.bioimageanalysis.icy.model.sequence.Sequence;
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.ezplug.*;
+import plugins.adufour.vars.lang.VarDouble;
+import plugins.adufour.vars.lang.VarEnum;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.adufour.vars.lang.VarSequence;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Toolbox to extract regions of interest (ROI) from a labeled image or sequence based on its
+ * connected components.<br>
+ * This toolbox supersedes the connected components toolbox
+ *
+ * @author Alexandre Dufour
+ */
+@IcyPluginName("Label Extractor")
+@IcyPluginIcon(path = "/label-extractor.png")
+public class LabelExtractorBlock extends Plugin implements Block {
+    VarSequence inSeq = new VarSequence("Labeled sequence", null);
+
+    VarEnum<ExtractionType> type = new VarEnum<>("Extract", ExtractionType.SPECIFIC_LABEL);
+
+    VarDouble value = new VarDouble("Value", 1d);
+
+    VarSequence outSeq = new VarSequence("Add ROI to", null);
+
+    VarROIArray outROI = new VarROIArray("Extracted ROI");
+
+    /**
+     * List of extraction methods suitable for the "ROI Extractor" toolbox
+     *
+     * @author Alexandre Dufour
+     */
+    public enum ExtractionType {
+        /**
+         * Extract all connected components with a value that is different from the background.<br>
+         * NB: Touching components with different labels will be <u>fused together</u>
+         */
+        ANY_LABEL_VS_BACKGROUND,
+        /**
+         * Extract all connected components with a value that is different from the background.<br>
+         * NB: Touching components with different labels are extracted <u>separately</u>
+         */
+        ALL_LABELS_VS_BACKGROUND,
+        /**
+         * Extracts all components with a specific value
+         */
+        SPECIFIC_LABEL;
+
+        @Override
+        public String toString() {
+            final String name = super.toString();
+            return name.charAt(0) + name.substring(1).toLowerCase().replace('_', ' ');
+        }
+    }
+
+    @Override
+    public void declareInput(final VarList inputMap) {
+        inputMap.add("input sequence", inSeq);
+        inputMap.add("extract mode", type);
+        inputMap.add("value", value);
+    }
+
+    @Override
+    public void declareOutput(final VarList outputMap) {
+        outputMap.add("ROI", outROI);
+    }
+
+    @Override
+    public void run() {
+        final List<ROI> rois = extractLabels(inSeq.getValue(true), type.getValue(), value.getValue());
+
+        outROI.setValue(rois.toArray(new ROI[0]));
+    }
+
+    /**
+     * A temporary component structure used during the extraction process. Many such components may
+     * be extracted from the sequence, and some are fused into the final extracted ROI
+     *
+     * @author Alexandre Dufour
+     */
+    private static class ConnectedComponent {
+        final double imageValue;
+
+        /**
+         * final label that should replace the current label if fusion is needed
+         */
+        int targetLabel;
+
+        /**
+         * if non-null, indicates the parent object with which the current object should be fused
+         */
+        ConnectedComponent targetComponent;
+
+        /**
+         * Creates a new label with the given value. If no parent is set to this label, the given
+         * value will be the final one
+         *
+         * @param value the pixel value
+         * @param label the label value
+         */
+        ConnectedComponent(final double value, final int label) {
+            this.imageValue = value;
+            this.targetLabel = label;
+        }
+
+        /**
+         * Retrieves the final object label (recursively)
+         */
+        int getTargetLabel() {
+            return targetComponent == null ? targetLabel : targetComponent.getTargetLabel();
+        }
+    }
+
+    /**
+     * Extract ROI in the specified sequence though its connected components (8-connectivity is
+     * considered in 2D, and 26-connectivity in 3D). The algorithm uses a fast two-pass image
+     * sweeping technique that builds a list of intermediate connected components which are
+     * eventually fused according to their connectivity.
+     *
+     * @param sequence      the sequence to extract connected components from
+     * @param whatToExtract the type of extraction to conduct (see the {@link ExtractionType} enumeration for
+     *                      available options
+     * @param value         this value is interpreted differently depending on the <code>whatToExtract</code>
+     *                      parameter:<br>
+     *                      <ul>
+     *                      <li>{@link ExtractionType#ALL_LABELS_VS_BACKGROUND}: the value is that of the
+     *                      background</li>
+     *                      <li>{@link ExtractionType#ANY_LABEL_VS_BACKGROUND}: the value is that of the
+     *                      background</li>
+     *                      <li>{@link ExtractionType#SPECIFIC_LABEL}: the value is that of the structures to
+     *                      extract</li>
+     *                      </ul>
+     * @return List of ROI
+     */
+    public static List<ROI> extractLabels(final Sequence sequence, final ExtractionType whatToExtract, final double value) {
+        final ArrayList<ROI> roi = new ArrayList<>();
+
+        int objID = 1;
+
+        for (int t = 0; t < sequence.getSizeT(); t++)
+            for (int c = 0; c < sequence.getSizeC(); c++) {
+                for (final ROI label : extractLabels(sequence, t, c, whatToExtract, value)) {
+                    // rename each ROI, but replace the ID
+                    final String shortName = label.getName().substring(label.getName().indexOf(" ("));
+                    label.setName("Object #" + objID++ + shortName);
+                    roi.add(label);
+                }
+            }
+
+        return roi;
+    }
+
+    /**
+     * Extract ROI in the specified sequence though its connected components (8-connectivity is
+     * considered in 2D, and 26-connectivity in 3D). The algorithm uses a fast single-pass sweeping
+     * technique that builds a list of intermediate connected components which are eventually fused
+     * according to their connectivity.<br>
+     * NB: in 3D, the algorithm caches some intermediate buffers to disk to save RAM
+     *
+     * @param sequence      the sequence to extract connected components from
+     * @param t             a specific time point where components should be extracted
+     * @param c             a specific channel where components should be extracted
+     * @param whatToExtract the type of extraction to conduct (see the {@link ExtractionType} enumeration for
+     *                      available options
+     * @param value         this value is interpreted differently depending on the <code>whatToExtract</code>
+     *                      parameter:<br>
+     *                      <ul>
+     *                      <li>{@link ExtractionType#ALL_LABELS_VS_BACKGROUND}: the value is that of the
+     *                      background</li>
+     *                      <li>{@link ExtractionType#ANY_LABEL_VS_BACKGROUND}: the value is that of the
+     *                      background</li>
+     *                      <li>{@link ExtractionType#SPECIFIC_LABEL}: the value is that of the structures to
+     *                      extract</li>
+     *                      </ul>
+     * @return List of ROI
+     */
+    public static List<ROI> extractLabels(final Sequence sequence, final int t, final int c, final ExtractionType whatToExtract, final double value) {
+        final int width = sequence.getSizeX();
+        final int height = sequence.getSizeY();
+        final int slice = width * height;
+        final int depth = sequence.getSizeZ();
+        final boolean is3D = depth > 1;
+        final DataType dataType = sequence.getDataType();
+
+        final Map<Integer, ConnectedComponent> ccs = new HashMap<>();
+        final Map<Integer, ROI> roiMap = new HashMap<>();
+
+        final int[] neighborLabels = new int[13];
+        int nbNeighbors = 0;
+
+        final boolean extractUserValue = (whatToExtract == ExtractionType.SPECIFIC_LABEL);
+
+        // temporary label buffer
+        final Sequence labelSequence = new Sequence();
+        boolean virtual = false;
+
+        int[] _labelsAbove = null;
+
+        // first image pass: naive labeling with simple backward neighborhood
+        int highestKnownLabel = 0;
+
+        for (int z = 0; z < depth; z++) {
+            int[] _labelsHere;
+
+            try {
+                _labelsHere = new int[slice];
+            }
+            catch (final OutOfMemoryError error) {
+                // not enough memory --> pass in virtual mode
+                if (!virtual) {
+                    labelSequence.setVolatile(true);
+                    virtual = true;
+
+                    // re-allocate (we should have enough memory now)
+                    _labelsHere = new int[slice];
+                }
+                else
+                    throw error;
+            }
+
+            // if (is3D) System.out.println("[Label Extractor] First pass (Z" + z + ")");
+
+            final Object inputData = sequence.getDataXY(t, z, c);
+
+            for (int y = 0, inOffset = 0; y < height; y++) {
+                if (Thread.currentThread().isInterrupted())
+                    return new ArrayList<>();
+
+                for (int x = 0; x < width; x++, inOffset++) {
+                    final double currentImageValue = Array1DUtil.getValue(inputData, inOffset, dataType);
+                    final boolean pixelEqualsUserValue = (currentImageValue == value);
+
+                    // do not process the current pixel if:
+                    // - extractUserValue is true and pixelEqualsUserValue is false
+                    // - extractUserValue is false and pixelEqualsUserValue is true
+
+                    if (extractUserValue != pixelEqualsUserValue)
+                        continue;
+
+                    // from here on, the current pixel should be labeled
+
+                    // -> look for existing labels in its neighborhood
+
+                    // 1) define the neighborhood of interest here
+                    // NB: this is a single pass method, so backward neighborhood is sufficient
+
+                    // legend:
+                    // e = edge
+                    // x = current pixel
+                    // n = valid neighbor
+                    // . = other neighbor
+
+                    if (z == 0) {
+                        if (y == 0) {
+                            if (x == 0) {
+                                // e e e
+                                // e x .
+                                // e . .
+
+                                // do nothing
+                            }
+                            else {
+                                // e e e
+                                // n x .
+                                // . . .
+
+                                neighborLabels[0] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 1;
+                            }
+                        }
+                        else {
+                            final int north = inOffset - width;
+
+                            if (x == 0) {
+                                // e n n
+                                // e x .
+                                // e . .
+
+                                neighborLabels[0] = _labelsHere[north];
+                                neighborLabels[1] = _labelsHere[north + 1];
+                                nbNeighbors = 2;
+                            }
+                            else if (x == width - 1) {
+                                // n n e
+                                // n x e
+                                // . . e
+
+                                neighborLabels[0] = _labelsHere[north - 1];
+                                neighborLabels[1] = _labelsHere[north];
+                                neighborLabels[2] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 3;
+                            }
+                            else {
+                                // n n n
+                                // n x .
+                                // . . .
+
+                                neighborLabels[0] = _labelsHere[north - 1];
+                                neighborLabels[1] = _labelsHere[north];
+                                neighborLabels[2] = _labelsHere[north + 1];
+                                neighborLabels[3] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 4;
+                            }
+                        }
+                    }
+                    else {
+                        if (y == 0) {
+                            final int south = inOffset + width;
+
+                            if (x == 0) {
+                                // e e e | e e e
+                                // e n n | e x .
+                                // e n n | e . .
+
+                                neighborLabels[0] = _labelsAbove[inOffset];
+                                neighborLabels[1] = _labelsAbove[inOffset + 1];
+                                neighborLabels[2] = _labelsAbove[south];
+                                neighborLabels[3] = _labelsAbove[south + 1];
+                                nbNeighbors = 4;
+                            }
+                            else if (x == width - 1) {
+                                // e e e | e e e
+                                // n n e | n x e
+                                // n n e | . . e
+
+                                neighborLabels[0] = _labelsAbove[inOffset - 1];
+                                neighborLabels[1] = _labelsAbove[inOffset];
+                                neighborLabels[2] = _labelsAbove[south - 1];
+                                neighborLabels[3] = _labelsAbove[south];
+                                neighborLabels[4] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 5;
+                            }
+                            else {
+                                // e e e | e e e
+                                // n n n | n x .
+                                // n n n | . . .
+
+                                neighborLabels[0] = _labelsAbove[inOffset - 1];
+                                neighborLabels[1] = _labelsAbove[inOffset];
+                                neighborLabels[2] = _labelsAbove[inOffset + 1];
+                                neighborLabels[3] = _labelsAbove[south - 1];
+                                neighborLabels[4] = _labelsAbove[south];
+                                neighborLabels[5] = _labelsAbove[south + 1];
+                                neighborLabels[6] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 7;
+                            }
+                        }
+                        else if (y == height - 1) {
+                            final int north = inOffset - width;
+
+                            if (x == 0) {
+                                // e n n | e n n
+                                // e n n | e x .
+                                // e e e | e e e
+
+                                neighborLabels[0] = _labelsAbove[north];
+                                neighborLabels[1] = _labelsAbove[north + 1];
+                                neighborLabels[2] = _labelsAbove[inOffset];
+                                neighborLabels[3] = _labelsAbove[inOffset + 1];
+                                neighborLabels[4] = _labelsHere[north];
+                                neighborLabels[5] = _labelsHere[north + 1];
+                                nbNeighbors = 6;
+                            }
+                            else if (x == width - 1) {
+                                // n n e | n n e
+                                // n n e | n x e
+                                // e e e | e e e
+
+                                neighborLabels[0] = _labelsAbove[north - 1];
+                                neighborLabels[1] = _labelsAbove[north];
+                                neighborLabels[2] = _labelsAbove[inOffset - 1];
+                                neighborLabels[3] = _labelsAbove[inOffset];
+                                neighborLabels[4] = _labelsHere[north - 1];
+                                neighborLabels[5] = _labelsHere[north];
+                                neighborLabels[6] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 7;
+                            }
+                            else {
+                                // n n n | n n n
+                                // n n n | n x .
+                                // e e e | e e e
+
+                                neighborLabels[0] = _labelsAbove[north - 1];
+                                neighborLabels[1] = _labelsAbove[north];
+                                neighborLabels[2] = _labelsAbove[north + 1];
+                                neighborLabels[3] = _labelsAbove[inOffset - 1];
+                                neighborLabels[4] = _labelsAbove[inOffset];
+                                neighborLabels[5] = _labelsAbove[inOffset + 1];
+                                neighborLabels[6] = _labelsHere[north - 1];
+                                neighborLabels[7] = _labelsHere[north];
+                                neighborLabels[8] = _labelsHere[north + 1];
+                                neighborLabels[9] = _labelsHere[inOffset - 1];
+                                nbNeighbors = 10;
+                            }
+                        }
+                        else {
+                            final int north = inOffset - width;
+                            final int south = inOffset + width;
+
+                            if (x == 0) {
+                                // e n n | e n n
+                                // e n n | e x .
+                                // e n n | e . .
+
+                                neighborLabels[0] = _labelsAbove[north];
+                                neighborLabels[1] = _labelsAbove[north + 1];
+                                neighborLabels[2] = _labelsAbove[inOffset];
+                                neighborLabels[3] = _labelsAbove[inOffset + 1];
+                                neighborLabels[4] = _labelsAbove[south];
+                                neighborLabels[5] = _labelsAbove[south + 1];
+                                neighborLabels[6] = _labelsHere[north];
+                                neighborLabels[7] = _labelsHere[north + 1];
+                                nbNeighbors = 8;
+                            }
+                            else if (x == width - 1) {
+                                final int northwest = north - 1;
+                                final int west = inOffset - 1;
+
+                                // n n e | n n e
+                                // n n e | n x e
+                                // n n e | . . e
+
+                                neighborLabels[0] = _labelsAbove[northwest];
+                                neighborLabels[1] = _labelsAbove[north];
+                                neighborLabels[2] = _labelsAbove[west];
+                                neighborLabels[3] = _labelsAbove[inOffset];
+                                neighborLabels[4] = _labelsAbove[south - 1];
+                                neighborLabels[5] = _labelsAbove[south];
+                                neighborLabels[6] = _labelsHere[northwest];
+                                neighborLabels[7] = _labelsHere[north];
+                                neighborLabels[8] = _labelsHere[west];
+                                nbNeighbors = 9;
+                            }
+                            else {
+                                final int northwest = north - 1;
+                                final int west = inOffset - 1;
+                                final int northeast = north + 1;
+                                final int southwest = south - 1;
+                                final int southeast = south + 1;
+
+                                // n n n | n n n
+                                // n n n | n x .
+                                // n n n | . . .
+
+                                neighborLabels[0] = _labelsAbove[northwest];
+                                neighborLabels[1] = _labelsAbove[north];
+                                neighborLabels[2] = _labelsAbove[northeast];
+                                neighborLabels[3] = _labelsAbove[west];
+                                neighborLabels[4] = _labelsAbove[inOffset];
+                                neighborLabels[5] = _labelsAbove[inOffset + 1];
+                                neighborLabels[6] = _labelsAbove[southwest];
+                                neighborLabels[7] = _labelsAbove[south];
+                                neighborLabels[8] = _labelsAbove[southeast];
+                                neighborLabels[9] = _labelsHere[northwest];
+                                neighborLabels[10] = _labelsHere[north];
+                                neighborLabels[11] = _labelsHere[northeast];
+                                neighborLabels[12] = _labelsHere[west];
+                                nbNeighbors = 13;
+                            }
+                        }
+                    }
+
+                    // 2) the neighborhood is ready, move to the labeling step
+
+                    // to avoid creating too many labels and fuse them later on,
+                    // find the minimum non-zero label in the neighborhood
+                    // and assign that minimum label right now
+
+                    int currentLabel = Integer.MAX_VALUE;
+
+                    for (int i = 0; i < nbNeighbors; i++) {
+                        final int neighborLabel = neighborLabels[i];
+
+                        // "zero" neighbors belong to the background...
+                        if (neighborLabel == 0)
+                            continue;
+
+                        if (whatToExtract == ExtractionType.ALL_LABELS_VS_BACKGROUND
+                                && ccs.get(neighborLabel).imageValue != currentImageValue)
+                            continue;
+
+                        // here, the neighbor label is valid
+                        // => check if it is lower
+                        if (neighborLabel < currentLabel) {
+                            currentLabel = neighborLabel;
+                        }
+                    }
+
+                    if (currentLabel == Integer.MAX_VALUE) {
+                        // currentLabel didn't change
+                        // => there is no lower neighbor
+                        // => register a new connected component
+                        highestKnownLabel++;
+                        currentLabel = highestKnownLabel;
+                        ccs.put(currentLabel, new ConnectedComponent(currentImageValue, currentLabel));
+                    }
+                    else {
+                        // currentLabel has been modified
+                        // -> browse the neighborhood again
+                        // --> fuse high labels with low labels
+
+                        final ConnectedComponent currentCC = ccs.get(currentLabel);
+                        final int currentTargetLabel = currentCC.getTargetLabel();
+
+                        for (int i = 0; i < nbNeighbors; i++) {
+                            final int neighborLabel = neighborLabels[i];
+
+                            if (neighborLabel == 0)
+                                continue; // no object in this pixel
+
+                            final ConnectedComponent neighborCC = ccs.get(neighborLabel);
+                            final int neighborTargetLabel = neighborCC.getTargetLabel();
+
+                            if (neighborTargetLabel == currentTargetLabel)
+                                continue;
+
+                            if (whatToExtract == ExtractionType.ALL_LABELS_VS_BACKGROUND
+                                    && neighborCC.imageValue != currentImageValue)
+                                continue;
+
+                            // fuse the highest with the lowest
+                            if (neighborTargetLabel > currentTargetLabel) {
+                                ccs.get(neighborTargetLabel).targetComponent = ccs.get(currentTargetLabel);
+                            }
+                            else {
+                                ccs.get(currentTargetLabel).targetComponent = ccs.get(neighborTargetLabel);
+                            }
+                        }
+                    }
+
+                    // -> store this label in the labeled image
+                    if (currentLabel != 0)
+                        _labelsHere[inOffset] = currentLabel;
+                }
+            }
+
+            final IcyBufferedImage img = new IcyBufferedImage(width, height, _labelsHere);
+            // pass to volatile if needed
+            if (virtual)
+                img.setVolatile(true);
+
+            // store image in label sequence
+            labelSequence.setImage(0, z, img);
+            // store labels from previous slice
+            _labelsAbove = _labelsHere;
+        }
+
+        // for debugging
+        // Sequence preLabels = new Sequence();
+        // preLabels.addImage(new IcyBufferedImage(width, height, new int[][] { _labels[0] }));
+        // preLabels.getColorModel().setColorMap(0, new FireColorMap(), true);
+        // addSequence(preLabels);
+
+        // end of the first pass, all pixels have a label
+        // (though might not be unique within a given component)
+
+        if (Thread.currentThread().isInterrupted())
+            return new ArrayList<>();
+
+        // fusion strategy: fuse higher labels with lower ones
+        // "highestKnownLabel" holds the highest known label
+        // -> loop downwards from there to accumulate object size recursively
+
+        int finalLabel = 0;
+
+        for (int currentLabel = highestKnownLabel; currentLabel > 0; currentLabel--) {
+            final ConnectedComponent currentCC = ccs.get(currentLabel);
+
+            // if the target label is higher than or equal to the current label
+            if (currentCC.targetLabel >= currentLabel) {
+                // label has same labelValue and targetLabelValue
+                // -> it cannot be fused to anything
+                // -> this is a terminal label
+
+                // -> assign its final labelValue (for the final image labeling pass)
+                finalLabel++;
+                currentCC.targetLabel = finalLabel;
+            }
+            else {
+                // the current label should be fused to targetLabel
+                currentCC.targetComponent = ccs.get(currentCC.targetLabel);
+            }
+        }
+
+        // 3) second image pass: replace all labels by their final values
+
+        finalPass:
+        for (int z = 0; z < depth; z++) {
+            // get labels for that slice
+            final int[] _labelsHere = (int[]) labelSequence.getDataXY(0, z, 0);
+
+            for (int j = 0, offset = 0; j < height; j++) {
+                if (Thread.currentThread().isInterrupted())
+                    break finalPass;
+
+                for (int i = 0; i < width; i++, offset++) {
+                    int targetLabel = _labelsHere[offset];
+
+                    if (targetLabel == 0)
+                        continue;
+
+                    // retrieve the image value (for naming purposes)
+                    final double imageValue = ccs.get(targetLabel).imageValue;
+
+                    // if a fusion was indicated, retrieve the final label value
+                    targetLabel = ccs.get(targetLabel).getTargetLabel();
+
+                    // store the current pixel in the component
+                    if (is3D) {
+                        if (!roiMap.containsKey(targetLabel)) {
+                            final ROI3DArea roi3D = new ROI3DArea();
+                            roi3D.setName("Object #" + targetLabel + " (value: " + imageValue + ")");
+                            roi3D.setT(t);
+                            roi3D.setC(c);
+                            roiMap.put(targetLabel, roi3D);
+                        }
+
+                        ((ROI3DArea) roiMap.get(targetLabel)).addPoint(i, j, z);
+                    }
+                    else {
+                        if (!roiMap.containsKey(targetLabel)) {
+                            final ROI2DArea roi2D = new ROI2DArea();
+                            roi2D.setName("Object #" + targetLabel + " (value: " + imageValue + ")");
+                            roi2D.setZ(0);
+                            roi2D.setT(t);
+                            roi2D.setC(c);
+                            roiMap.put(targetLabel, roi2D);
+                        }
+
+                        ((ROI2DArea) roiMap.get(targetLabel)).addPoint(i, j);
+                    }
+                }
+            }
+        }
+
+        return new ArrayList<>(roiMap.values());
+    }
+}