diff --git a/src/main/java/plugins/adufour/projection/Projection.java b/src/main/java/plugins/adufour/projection/Projection.java
index 6e85f6af86e56e5c12124dc545874309a42f6030..78e98bad685966c5bdd81c731cf6458120eade59 100644
--- a/src/main/java/plugins/adufour/projection/Projection.java
+++ b/src/main/java/plugins/adufour/projection/Projection.java
@@ -3,10 +3,9 @@ package plugins.adufour.projection;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.List;
 import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
 
 import icy.image.IcyBufferedImage;
 import icy.main.Icy;
@@ -15,10 +14,11 @@ import icy.plugin.PluginLauncher;
 import icy.plugin.PluginLoader;
 import icy.roi.ROI;
 import icy.sequence.Sequence;
-import icy.sequence.SequenceUtil;
 import icy.system.SystemUtil;
+import icy.system.thread.Processor;
 import icy.type.DataType;
 import icy.type.collection.array.Array1DUtil;
+import icy.util.OMEUtil;
 import plugins.adufour.blocks.lang.Block;
 import plugins.adufour.blocks.util.VarList;
 import plugins.adufour.ezplug.EzPlug;
@@ -86,12 +86,12 @@ public class Projection extends EzPlug implements Block, EzStoppable
         switch (projectionDir.getValue())
         {
             case T:
-                output.setValue(
-                        tProjection(input.getValue(true), projectionType.getValue(), true, restrictToROI.getValue()));
+                output.setValue(tProjection(input.getValue(true), projectionType.getValue(), true,
+                        restrictToROI.getValue().booleanValue()));
                 break;
             case Z:
-                output.setValue(
-                        zProjection(input.getValue(true), projectionType.getValue(), true, restrictToROI.getValue()));
+                output.setValue(zProjection(input.getValue(true), projectionType.getValue(), true,
+                        restrictToROI.getValue().booleanValue()));
                 break;
             default:
                 throw new UnsupportedOperationException(
@@ -105,7 +105,22 @@ public class Projection extends EzPlug implements Block, EzStoppable
     @Override
     public void clean()
     {
+        //
+    }
 
+    /**
+     * Performs a Z projection of the input sequence using the specified algorithm. If the sequence
+     * is already 2D, then a copy of the sequence is returned
+     * 
+     * @param in
+     *        the sequence to project
+     * @param projection
+     *        the type of projection to perform (see {@link ProjectionType} enumeration)
+     * @return the projected sequence
+     */
+    public static Sequence zProjection(final Sequence in, final ProjectionType projection)
+    {
+        return zProjection(in, projection, true, false);
     }
 
     /**
@@ -117,7 +132,7 @@ public class Projection extends EzPlug implements Block, EzStoppable
      * @param projection
      *        the type of projection to perform (see {@link ProjectionType} enumeration)
      * @param multiThread
-     *        true if the process should be multi-threaded
+     *        deprecated (there is not reason to not use it)
      * @return the projected sequence
      */
     public static Sequence zProjection(final Sequence in, final ProjectionType projection, boolean multiThread)
@@ -134,7 +149,7 @@ public class Projection extends EzPlug implements Block, EzStoppable
      * @param projection
      *        the type of projection to perform (see {@link ProjectionType} enumeration)
      * @param multiThread
-     *        true if the process should be multi-threaded
+     *        deprecated (there is not reason to not use it)
      * @param restrictToROI
      *        <code>true</code> projects only data located within the sequence ROI,
      *        <code>false</code> projects the entire data set
@@ -143,331 +158,328 @@ public class Projection extends EzPlug implements Block, EzStoppable
     public static Sequence zProjection(final Sequence in, final ProjectionType projection, boolean multiThread,
             boolean restrictToROI)
     {
-        final int depth = in.getSizeZ();
-        if (depth == 1 && !restrictToROI)
-            return SequenceUtil.getCopy(in);
-
-        final Sequence out = new Sequence(projection.name() + " projection of " + in.getName());
-        out.copyMetaDataFrom(in, false);
+        final Sequence out = new Sequence(OMEUtil.createOMEXMLMetadata(in.getOMEXMLMetadata()),
+                projection.name() + " projection of " + in.getName());
 
         final int width = in.getSizeX();
         final int height = in.getSizeY();
+        final int depth = in.getSizeZ();
         final int frames = in.getSizeT();
         final int channels = in.getSizeC();
         final DataType dataType = in.getDataType_();
 
         final Collection<ROI> rois = in.getROISet();
         final boolean processROI = restrictToROI && (rois.size() > 0);
-        final int cpus = SystemUtil.getNumberOfCPUs();
 
-        ExecutorService service = multiThread ? Executors.newFixedThreadPool(cpus)
-                : Executors.newSingleThreadExecutor();
-        ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
+        final Processor processor = new Processor(Math.max(1024, height), SystemUtil.getNumberOfCPUs());
+        final List<Future<?>> futures = new ArrayList<Future<?>>();
 
         for (int frame = 0; frame < frames; frame++)
         {
-            if (Thread.currentThread().isInterrupted())
-            {
-                // stop all task now
-                service.shutdownNow();
-                break;
-            }
-
             final int t = frame;
 
             // set new image in result sequence
-            out.setImage(t, 0, new IcyBufferedImage(width, height, channels, dataType));
+            final IcyBufferedImage resultImg = new IcyBufferedImage(width, height, channels, dataType);
+            // to optimize image access in main loop !
+            final IcyBufferedImage[] images = in.getImages(t).toArray(new IcyBufferedImage[0]);
 
             for (int channel = 0; channel < channels; channel++)
             {
                 final int c = channel;
-                // to optimize image access in main loop !
-                final IcyBufferedImage[] imagesZ = in.getImages(t).toArray(new IcyBufferedImage[0]);
+                // fast access to result pixel data for this channel
+                final Object resultData = resultImg.getDataXY(c);
 
-                futures.add(service.submit(new Runnable()
+                try
                 {
-                    @Override
-                    public void run()
+                    for (int line = 0; line < height; line++)
                     {
-                        final IcyBufferedImage resultImg = out.getImage(t, 0);
-                        final Object resultData = resultImg.getDataXY(c);
-                        int offset = 0;
+                        final int y = line;
 
-                        for (int y = 0; y < height; y++)
+                        futures.add(processor.submit(new Runnable()
                         {
-                            for (int x = 0; x < width; x++, offset++)
+                            @Override
+                            public void run()
                             {
-                                double[] stackPixels = new double[depth];
-                                int nbPixel = 0;
+                                double[] pixelStack = new double[depth];
+                                int offset = y * width;
 
-                                for (int z = 0; z < depth; z++)
+                                for (int x = 0; x < width; x++, offset++)
                                 {
-                                    boolean processPixel;
+                                    int nbPixel = 0;
 
-                                    if (processROI)
+                                    for (int z = 0; z < depth; z++)
                                     {
-                                        processPixel = false;
+                                        boolean processPixel;
 
-                                        for (ROI roi : rois)
+                                        if (processROI)
                                         {
-                                            if (roi.contains(x, y, z, t, c))
+                                            processPixel = false;
+
+                                            for (ROI roi : rois)
                                             {
-                                                processPixel = true;
-                                                break;
+                                                if (roi.contains(x, y, z, t, c))
+                                                {
+                                                    processPixel = true;
+                                                    break;
+                                                }
                                             }
                                         }
-                                    }
-                                    else
-                                        processPixel = true;
+                                        else
+                                            processPixel = true;
 
-                                    if (processPixel)
-                                        stackPixels[nbPixel++] = imagesZ[z].getData(x, y, c);
-                                }
+                                        if (processPixel)
+                                            pixelStack[nbPixel++] = images[z].getData(x, y, c);
+                                    }
 
-                                if (nbPixel == 0)
-                                    continue;
+                                    // no pixel processed here ? --> next
+                                    if (nbPixel == 0)
+                                        continue;
 
-                                // adjust pixel array size if needed
-                                stackPixels = Arrays.copyOf(stackPixels, nbPixel);
+                                    final double[] pixels;
 
-                                double result = 0d;
+                                    // adjust pixel array size if needed
+                                    if (pixelStack.length > nbPixel)
+                                        pixels = Arrays.copyOf(pixelStack, nbPixel);
+                                    else
+                                        pixels = pixelStack;
 
-                                switch (projection)
-                                {
-                                    case MAX:
-                                        result = ArrayMath.max(stackPixels);
-                                        break;
-                                    case MEAN:
-                                        result = ArrayMath.mean(stackPixels);
-                                        break;
-                                    case MED:
-                                        result = ArrayMath.median(stackPixels, false);
-                                        break;
-                                    case MIN:
-                                        result = ArrayMath.min(stackPixels);
-                                        break;
-                                    case STD:
-                                        result = ArrayMath.std(stackPixels, true);
-                                        break;
-                                    case SATSUM:
-                                        result = ArrayMath.sum(stackPixels);
-                                        break;
-                                    default:
-                                        throw new UnsupportedOperationException(
-                                                projection + " intensity projection not implemented");
-                                }
+                                    double result = 0d;
 
-                                // set result in data array
-                                Array1DUtil.setValue(resultData, offset, dataType, result);
-                            }
+                                    switch (projection)
+                                    {
+                                        case MAX:
+                                            result = ArrayMath.max(pixels);
+                                            break;
+                                        case MEAN:
+                                            result = ArrayMath.mean(pixels);
+                                            break;
+                                        case MED:
+                                            result = ArrayMath.median(pixels, false);
+                                            break;
+                                        case MIN:
+                                            result = ArrayMath.min(pixels);
+                                            break;
+                                        case STD:
+                                            result = ArrayMath.std(pixels, true);
+                                            break;
+                                        case SATSUM:
+                                            result = ArrayMath.sum(pixels);
+                                            break;
+                                        default:
+                                            throw new UnsupportedOperationException(
+                                                    projection + " intensity projection not implemented");
+                                    }
 
-                            // task interrupted ?
-                            if (Thread.currentThread().isInterrupted())
-                            {
-                                // propagate partial changes and stop here
-                                resultImg.setDataXY(c, resultData);
-                                return;
+                                    // set result in data array
+                                    Array1DUtil.setValue(resultData, offset, dataType, result);
+                                }
                             }
-                        }
-
-                        // data changed and cache update
-                        resultImg.setDataXY(c, resultData);
+                        }));
                     }
-                }));
-            }
-        }
-
-        try
-        {
-            for (Future<?> future : futures)
-                future.get();
-        }
-        catch (InterruptedException e)
-        {
-            // ignore
-            service.shutdownNow();
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-
-        service.shutdown();
-
-        // Copy color map information
-        for (int c = 0; c < in.getSizeC(); c++)
-            out.getColorModel().setColorMap(c, in.getColorMap(c), true);
-
-        return out;
-    }
-
-    /**
-     * Performs a Z projection of the input sequence using the specified algorithm. If the sequence
-     * is already 2D, then a copy of the sequence is returned
-     * 
-     * @param in
-     *        the sequence to project
-     * @param projection
-     *        the type of projection to perform (see {@link ProjectionType} enumeration)
-     * @param multiThread
-     *        true if the process should be multi-threaded
-     * @param restrictToROI
-     *        <code>true</code> projects only data located within the sequence ROI,
-     *        <code>false</code> projects the entire data set
-     * @return the projected sequence
-     */
-    public static Sequence zProjection_(final Sequence in, final ProjectionType projection, boolean multiThread,
-            boolean restrictToROI)
-    {
-        final int depth = in.getSizeZ();
-        if (depth == 1 && !restrictToROI)
-            return SequenceUtil.getCopy(in);
-
-        final Sequence out = new Sequence(projection.name() + " projection of " + in.getName());
-        out.copyMetaDataFrom(in, false);
-
-        final int width = in.getSizeX();
-        final int height = in.getSizeY();
-        final int frames = in.getSizeT();
-        final int channels = in.getSizeC();
-        final DataType dataType = in.getDataType_();
-
-        final Collection<ROI> rois = in.getROISet();
-        final boolean processROI = restrictToROI && rois.size() > 0;
-
-        int cpus = SystemUtil.getNumberOfCPUs();
-        int chunkSize = width * height / cpus;
-        final int[] minOffsets = new int[cpus];
-        final int[] maxOffsets = new int[cpus];
-        for (int cpu = 0; cpu < cpus; cpu++)
-        {
-            minOffsets[cpu] = chunkSize * cpu;
-            maxOffsets[cpu] = chunkSize * (cpu + 1);
-        }
-        // NB: the last chunk must include the remaining pixels (in case rounding off occurs)
-        maxOffsets[cpus - 1] = width * height;
 
-        ExecutorService service = multiThread ? Executors.newFixedThreadPool(cpus)
-                : Executors.newSingleThreadExecutor();
-        ArrayList<Future<?>> futures = new ArrayList<Future<?>>(channels * frames * cpus);
-
-        for (int frame = 0; frame < frames; frame++)
-        {
-            final int t = frame;
-
-            if (Thread.currentThread().isInterrupted())
-                break;
-
-            out.setImage(t, 0, new IcyBufferedImage(width, height, channels, dataType));
-
-            for (int channel = 0; channel < channels; channel++)
-            {
-                final int c = channel;
-                final Object[] in_Z_XY = (Object[]) in.getDataXYZ(t, c);
-                final Object out_Z_XY = out.getDataXY(t, 0, c);
-
-                for (int cpu = 0; cpu < cpus; cpu++)
+                    // wait for completion for tasks
+                    for (Future<?> future : futures)
+                        future.get();
+                }
+                catch (RejectedExecutionException e)
                 {
-                    final int minOffset = minOffsets[cpu];
-                    final int maxOffset = maxOffsets[cpu];
-
-                    futures.add(service.submit(new Runnable()
-                    {
-                        @Override
-                        public void run()
-                        {
-                            double[] buffer = new double[depth];
-                            double[] dataToProject = null;
-
-                            for (int offset = minOffset; offset < maxOffset; offset++)
-                            {
-                                if (processROI)
-                                {
-                                    int x = offset % width;
-                                    int y = offset / width;
-
-                                    int nbValues = 0;
-
-                                    for (int z = 0; z < depth; z++)
-                                        for (ROI roi : rois)
-                                            if (roi.contains(x, y, z, t, c))
-                                            {
-                                                buffer[nbValues++] = Array1DUtil.getValue(in_Z_XY[z], offset, dataType);
-                                                break;
-                                            }
-
-                                    if (nbValues == 0)
-                                        continue;
-
-                                    dataToProject = (nbValues == buffer.length) ? buffer
-                                            : Arrays.copyOf(buffer, nbValues);
-                                }
-                                else
-                                {
-                                    for (int z = 0; z < depth; z++)
-                                        buffer[z] = Array1DUtil.getValue(in_Z_XY[z], offset, dataType);
-                                    dataToProject = buffer;
-                                }
-
-                                switch (projection)
-                                {
-                                    case MAX:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.max(dataToProject));
-                                        break;
-                                    case MEAN:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.mean(dataToProject));
-                                        break;
-                                    case MED:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType,
-                                                ArrayMath.median(dataToProject, false));
-                                        break;
-                                    case MIN:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.min(dataToProject));
-                                        break;
-                                    case STD:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType,
-                                                ArrayMath.std(dataToProject, true));
-                                        break;
-                                    case SATSUM:
-                                        Array1DUtil.setValue(out_Z_XY, offset, dataType,
-                                                Math.min(ArrayMath.sum(dataToProject), dataType.getMaxValue()));
-                                        break;
-                                    default:
-                                        throw new UnsupportedOperationException(
-                                                projection + " intensity projection not implemented");
-                                }
-                            } // offset
-                        }
-                    }));
+                    // mean that we were interrupted
+                    processor.shutdownNow();
+                }
+                catch (InterruptedException e)
+                {
+                    // ignore
+                    processor.shutdownNow();
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
                 }
+
+                // data changed and cache update
+                resultImg.setDataXY(c, resultData);
             }
-        }
 
-        try
-        {
-            for (Future<?> future : futures)
-                future.get();
-        }
-        catch (InterruptedException iE)
-        {
-            Thread.currentThread().interrupt();
-        }
-        catch (ExecutionException eE)
-        {
-            throw new RuntimeException(eE);
+            // set image in sequence
+            out.setImage(t, 0, resultImg);
         }
 
-        service.shutdown();
+        processor.shutdown();
 
         // Copy color map information
         for (int c = 0; c < in.getSizeC(); c++)
             out.getColorModel().setColorMap(c, in.getColorMap(c), true);
 
-        out.dataChanged();
-
         return out;
     }
 
+    // /**
+    // * Performs a Z projection of the input sequence using the specified algorithm. If the sequence
+    // * is already 2D, then a copy of the sequence is returned
+    // *
+    // * @param in
+    // * the sequence to project
+    // * @param projection
+    // * the type of projection to perform (see {@link ProjectionType} enumeration)
+    // * @param multiThread
+    // * true if the process should be multi-threaded
+    // * @param restrictToROI
+    // * <code>true</code> projects only data located within the sequence ROI,
+    // * <code>false</code> projects the entire data set
+    // * @return the projected sequence
+    // */
+    // public static Sequence zProjection_(final Sequence in, final ProjectionType projection, boolean multiThread,
+    // boolean restrictToROI)
+    // {
+    // final int depth = in.getSizeZ();
+    // if (depth == 1 && !restrictToROI)
+    // return SequenceUtil.getCopy(in);
+    //
+    // final Sequence out = new Sequence(projection.name() + " projection of " + in.getName());
+    // out.copyMetaDataFrom(in, false);
+    //
+    // final int width = in.getSizeX();
+    // final int height = in.getSizeY();
+    // final int frames = in.getSizeT();
+    // final int channels = in.getSizeC();
+    // final DataType dataType = in.getDataType_();
+    //
+    // final Collection<ROI> rois = in.getROISet();
+    // final boolean processROI = restrictToROI && rois.size() > 0;
+    //
+    // int cpus = SystemUtil.getNumberOfCPUs();
+    // int chunkSize = width * height / cpus;
+    // final int[] minOffsets = new int[cpus];
+    // final int[] maxOffsets = new int[cpus];
+    // for (int cpu = 0; cpu < cpus; cpu++)
+    // {
+    // minOffsets[cpu] = chunkSize * cpu;
+    // maxOffsets[cpu] = chunkSize * (cpu + 1);
+    // }
+    // // NB: the last chunk must include the remaining pixels (in case rounding off occurs)
+    // maxOffsets[cpus - 1] = width * height;
+    //
+    // ExecutorService service = multiThread ? Executors.newFixedThreadPool(cpus)
+    // : Executors.newSingleThreadExecutor();
+    // ArrayList<Future<?>> futures = new ArrayList<Future<?>>(channels * frames * cpus);
+    //
+    // for (int frame = 0; frame < frames; frame++)
+    // {
+    // final int t = frame;
+    //
+    // if (Thread.currentThread().isInterrupted())
+    // break;
+    //
+    // out.setImage(t, 0, new IcyBufferedImage(width, height, channels, dataType));
+    //
+    // for (int channel = 0; channel < channels; channel++)
+    // {
+    // final int c = channel;
+    // final Object[] in_Z_XY = (Object[]) in.getDataXYZ(t, c);
+    // final Object out_Z_XY = out.getDataXY(t, 0, c);
+    //
+    // for (int cpu = 0; cpu < cpus; cpu++)
+    // {
+    // final int minOffset = minOffsets[cpu];
+    // final int maxOffset = maxOffsets[cpu];
+    //
+    // futures.add(service.submit(new Runnable()
+    // {
+    // @Override
+    // public void run()
+    // {
+    // double[] buffer = new double[depth];
+    // double[] dataToProject = null;
+    //
+    // for (int offset = minOffset; offset < maxOffset; offset++)
+    // {
+    // if (processROI)
+    // {
+    // int x = offset % width;
+    // int y = offset / width;
+    //
+    // int nbValues = 0;
+    //
+    // for (int z = 0; z < depth; z++)
+    // for (ROI roi : rois)
+    // if (roi.contains(x, y, z, t, c))
+    // {
+    // buffer[nbValues++] = Array1DUtil.getValue(in_Z_XY[z], offset, dataType);
+    // break;
+    // }
+    //
+    // if (nbValues == 0)
+    // continue;
+    //
+    // dataToProject = (nbValues == buffer.length) ? buffer
+    // : Arrays.copyOf(buffer, nbValues);
+    // }
+    // else
+    // {
+    // for (int z = 0; z < depth; z++)
+    // buffer[z] = Array1DUtil.getValue(in_Z_XY[z], offset, dataType);
+    // dataToProject = buffer;
+    // }
+    //
+    // switch (projection)
+    // {
+    // case MAX:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.max(dataToProject));
+    // break;
+    // case MEAN:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.mean(dataToProject));
+    // break;
+    // case MED:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType,
+    // ArrayMath.median(dataToProject, false));
+    // break;
+    // case MIN:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType, ArrayMath.min(dataToProject));
+    // break;
+    // case STD:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType,
+    // ArrayMath.std(dataToProject, true));
+    // break;
+    // case SATSUM:
+    // Array1DUtil.setValue(out_Z_XY, offset, dataType,
+    // Math.min(ArrayMath.sum(dataToProject), dataType.getMaxValue()));
+    // break;
+    // default:
+    // throw new UnsupportedOperationException(
+    // projection + " intensity projection not implemented");
+    // }
+    // } // offset
+    // }
+    // }));
+    // }
+    // }
+    // }
+    //
+    // try
+    // {
+    // for (Future<?> future : futures)
+    // future.get();
+    // }
+    // catch (InterruptedException iE)
+    // {
+    // Thread.currentThread().interrupt();
+    // }
+    // catch (ExecutionException eE)
+    // {
+    // throw new RuntimeException(eE);
+    // }
+    //
+    // service.shutdown();
+    //
+    // // Copy color map information
+    // for (int c = 0; c < in.getSizeC(); c++)
+    // out.getColorModel().setColorMap(c, in.getColorMap(c), true);
+    //
+    // out.dataChanged();
+    //
+    // return out;
+    // }
+
     /**
      * Performs a T projection of the input sequence using the specified algorithm. If the sequence
      * has only one time point, then a copy of the sequence is returned
@@ -503,159 +515,320 @@ public class Projection extends EzPlug implements Block, EzStoppable
     public static Sequence tProjection(final Sequence in, final ProjectionType projection, boolean multiThread,
             boolean restrictToROI)
     {
-        final int frames = in.getSizeT();
-        if (frames == 1 && !restrictToROI)
-            return SequenceUtil.getCopy(in);
-
-        final Sequence out = new Sequence(projection.name() + " projection of " + in.getName());
-        out.copyMetaDataFrom(in, false);
+        final Sequence out = new Sequence(OMEUtil.createOMEXMLMetadata(in.getOMEXMLMetadata()),
+                projection.name() + " projection of " + in.getName());
 
         final int width = in.getSizeX();
         final int height = in.getSizeY();
         final int depth = in.getSizeZ();
+        final int frames = in.getSizeT();
         final int channels = in.getSizeC();
         final DataType dataType = in.getDataType_();
 
         final Collection<ROI> rois = in.getROISet();
         final boolean processROI = restrictToROI && (rois.size() > 0);
-        final int cpus = SystemUtil.getNumberOfCPUs();
 
-        ExecutorService service = multiThread ? Executors.newFixedThreadPool(cpus)
-                : Executors.newSingleThreadExecutor();
-        ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
+        final Processor processor = new Processor(Math.max(1024, height), SystemUtil.getNumberOfCPUs());
+        final List<Future<?>> futures = new ArrayList<Future<?>>();
+
+        // to optimize image access in main loop !
+        final IcyBufferedImage[] images = new IcyBufferedImage[depth * frames];
+        for (int z = 0; z < depth; z++)
+            for (int t = 0; t < frames; t++)
+                images[(z * frames) + t] = in.getImage(t, z);
 
         for (int slice = 0; slice < depth; slice++)
         {
-            if (Thread.currentThread().isInterrupted())
-            {
-                // stop all task now
-                service.shutdownNow();
-                break;
-            }
-
             final int z = slice;
+            final int imgOff = z * frames;
 
             // set new image in result sequence
-            out.setImage(0, z, new IcyBufferedImage(width, height, channels, dataType));
+            final IcyBufferedImage resultImg = new IcyBufferedImage(width, height, channels, dataType);
 
             for (int channel = 0; channel < channels; channel++)
             {
                 final int c = channel;
+                // fast access to result pixel data for this channel
+                final Object resultData = resultImg.getDataXY(c);
 
-                futures.add(service.submit(new Runnable()
+                try
                 {
-                    @Override
-                    public void run()
+                    for (int line = 0; line < height; line++)
                     {
-                        final IcyBufferedImage resultImg = out.getImage(0, z);
-                        final Object resultData = resultImg.getDataXY(c);
-                        int offset = 0;
+                        final int y = line;
 
-                        for (int y = 0; y < height; y++)
+                        futures.add(processor.submit(new Runnable()
                         {
-                            for (int x = 0; x < width; x++, offset++)
+                            @Override
+                            public void run()
                             {
-                                double[] framePixels = new double[frames];
-                                int nbPixel = 0;
+                                double[] pixelFrames = new double[frames];
+                                int offset = y * width;
 
-                                for (int t = 0; t < frames; t++)
+                                for (int x = 0; x < width; x++, offset++)
                                 {
-                                    boolean processPixel;
+                                    int nbPixel = 0;
 
-                                    if (processROI)
+                                    for (int t = 0; t < frames; t++)
                                     {
-                                        processPixel = false;
+                                        boolean processPixel;
 
-                                        for (ROI roi : rois)
+                                        if (processROI)
                                         {
-                                            if (roi.contains(x, y, z, t, c))
+                                            processPixel = false;
+
+                                            for (ROI roi : rois)
                                             {
-                                                processPixel = true;
-                                                break;
+                                                if (roi.contains(x, y, z, t, c))
+                                                {
+                                                    processPixel = true;
+                                                    break;
+                                                }
                                             }
                                         }
+                                        else
+                                            processPixel = true;
+
+                                        if (processPixel)
+                                            pixelFrames[nbPixel++] = images[imgOff + t].getData(x, y, c);
                                     }
-                                    else
-                                        processPixel = true;
 
-                                    if (processPixel)
-                                        framePixels[nbPixel++] = in.getData(t, z, c, y, x);
-                                }
+                                    // no pixel processed here ? --> next
+                                    if (nbPixel == 0)
+                                        continue;
 
-                                if (nbPixel == 0)
-                                    continue;
+                                    final double[] pixels;
 
-                                // adjust pixel array size if needed
-                                framePixels = Arrays.copyOf(framePixels, nbPixel);
+                                    // adjust pixel array size if needed
+                                    if (pixelFrames.length > nbPixel)
+                                        pixels = Arrays.copyOf(pixelFrames, nbPixel);
+                                    else
+                                        pixels = pixelFrames;
 
-                                double result = 0d;
+                                    double result = 0d;
 
-                                switch (projection)
-                                {
-                                    case MAX:
-                                        result = ArrayMath.max(framePixels);
-                                        break;
-                                    case MEAN:
-                                        result = ArrayMath.mean(framePixels);
-                                        break;
-                                    case MED:
-                                        result = ArrayMath.median(framePixels, false);
-                                        break;
-                                    case MIN:
-                                        result = ArrayMath.min(framePixels);
-                                        break;
-                                    case STD:
-                                        result = ArrayMath.std(framePixels, true);
-                                        break;
-                                    case SATSUM:
-                                        result = ArrayMath.sum(framePixels);
-                                        break;
-                                    default:
-                                        throw new UnsupportedOperationException(
-                                                projection + " intensity projection not implemented");
-                                }
+                                    switch (projection)
+                                    {
+                                        case MAX:
+                                            result = ArrayMath.max(pixels);
+                                            break;
+                                        case MEAN:
+                                            result = ArrayMath.mean(pixels);
+                                            break;
+                                        case MED:
+                                            result = ArrayMath.median(pixels, false);
+                                            break;
+                                        case MIN:
+                                            result = ArrayMath.min(pixels);
+                                            break;
+                                        case STD:
+                                            result = ArrayMath.std(pixels, true);
+                                            break;
+                                        case SATSUM:
+                                            result = ArrayMath.sum(pixels);
+                                            break;
+                                        default:
+                                            throw new UnsupportedOperationException(
+                                                    projection + " intensity projection not implemented");
+                                    }
 
-                                // set result in data array
-                                Array1DUtil.setValue(resultData, offset, dataType, result);
+                                    // set result in data array
+                                    Array1DUtil.setValue(resultData, offset, dataType, result);
+                                }
                             }
+                        }));
+                    }
 
-                            // task interrupted ?
-                            if (Thread.currentThread().isInterrupted())
-                            {
-                                // propagate partial changes and stop here
-                                resultImg.setDataXY(c, resultData);
-                                return;
-                            }
-                        }
+                    // wait for completion for tasks
+                    for (Future<?> future : futures)
+                        future.get();
+                }
+                catch (RejectedExecutionException e)
+                {
+                    // mean that we were interrupted
+                    processor.shutdownNow();
+                }
+                catch (InterruptedException e)
+                {
+                    // ignore
+                    processor.shutdownNow();
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
 
-                        // data changed and cache update
-                        resultImg.setDataXY(c, resultData);
-                    }
-                }));
+                // data changed and cache update
+                resultImg.setDataXY(c, resultData);
             }
-        }
 
-        try
-        {
-            for (Future<?> future : futures)
-                future.get();
-        }
-        catch (InterruptedException e)
-        {
-            service.shutdownNow();
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
+            // set image in sequence
+            out.setImage(0, z, resultImg);
         }
 
-        service.shutdown();
+        processor.shutdown();
 
         // Copy color map information
         for (int c = 0; c < in.getSizeC(); c++)
             out.getColorModel().setColorMap(c, in.getColorMap(c), true);
 
         return out;
+
+        /*
+         * 
+         * 
+         * final Sequence out = new Sequence(OMEUtil.createOMEXMLMetadata(in.getOMEXMLMetadata()),
+         * projection.name() + " projection of " + in.getName());
+         * 
+         * final int width = in.getSizeX();
+         * final int height = in.getSizeY();
+         * final int depth = in.getSizeZ();
+         * final int frames = in.getSizeT();
+         * final int channels = in.getSizeC();
+         * final DataType dataType = in.getDataType_();
+         * 
+         * final Collection<ROI> rois = in.getROISet();
+         * final boolean processROI = restrictToROI && (rois.size() > 0);
+         * final int cpus = SystemUtil.getNumberOfCPUs();
+         * 
+         * ExecutorService service = multiThread ? Executors.newFixedThreadPool(cpus)
+         * : Executors.newSingleThreadExecutor();
+         * ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
+         * 
+         * for (int slice = 0; slice < depth; slice++)
+         * {
+         * if (Thread.currentThread().isInterrupted())
+         * {
+         * // stop all task now
+         * service.shutdownNow();
+         * break;
+         * }
+         * 
+         * final int z = slice;
+         * 
+         * // set new image in result sequence
+         * out.setImage(0, z, new IcyBufferedImage(width, height, channels, dataType));
+         * 
+         * for (int channel = 0; channel < channels; channel++)
+         * {
+         * final int c = channel;
+         * 
+         * futures.add(service.submit(new Runnable()
+         * {
+         * 
+         * @Override
+         * public void run()
+         * {
+         * final IcyBufferedImage resultImg = out.getImage(0, z);
+         * final Object resultData = resultImg.getDataXY(c);
+         * int offset = 0;
+         * 
+         * for (int y = 0; y < height; y++)
+         * {
+         * for (int x = 0; x < width; x++, offset++)
+         * {
+         * double[] framePixels = new double[frames];
+         * int nbPixel = 0;
+         * 
+         * for (int t = 0; t < frames; t++)
+         * {
+         * boolean processPixel;
+         * 
+         * if (processROI)
+         * {
+         * processPixel = false;
+         * 
+         * for (ROI roi : rois)
+         * {
+         * if (roi.contains(x, y, z, t, c))
+         * {
+         * processPixel = true;
+         * break;
+         * }
+         * }
+         * }
+         * else
+         * processPixel = true;
+         * 
+         * if (processPixel)
+         * framePixels[nbPixel++] = in.getData(t, z, c, y, x);
+         * }
+         * 
+         * if (nbPixel == 0)
+         * continue;
+         * 
+         * // adjust pixel array size if needed
+         * framePixels = Arrays.copyOf(framePixels, nbPixel);
+         * 
+         * double result = 0d;
+         * 
+         * switch (projection)
+         * {
+         * case MAX:
+         * result = ArrayMath.max(framePixels);
+         * break;
+         * case MEAN:
+         * result = ArrayMath.mean(framePixels);
+         * break;
+         * case MED:
+         * result = ArrayMath.median(framePixels, false);
+         * break;
+         * case MIN:
+         * result = ArrayMath.min(framePixels);
+         * break;
+         * case STD:
+         * result = ArrayMath.std(framePixels, true);
+         * break;
+         * case SATSUM:
+         * result = ArrayMath.sum(framePixels);
+         * break;
+         * default:
+         * throw new UnsupportedOperationException(
+         * projection + " intensity projection not implemented");
+         * }
+         * 
+         * // set result in data array
+         * Array1DUtil.setValue(resultData, offset, dataType, result);
+         * }
+         * 
+         * // task interrupted ?
+         * if (Thread.currentThread().isInterrupted())
+         * {
+         * // propagate partial changes and stop here
+         * resultImg.setDataXY(c, resultData);
+         * return;
+         * }
+         * }
+         * 
+         * // data changed and cache update
+         * resultImg.setDataXY(c, resultData);
+         * }
+         * }));
+         * }
+         * }
+         * 
+         * try
+         * {
+         * for (Future<?> future : futures)
+         * future.get();
+         * }
+         * catch (InterruptedException e)
+         * {
+         * service.shutdownNow();
+         * }
+         * catch (Exception e)
+         * {
+         * throw new RuntimeException(e);
+         * }
+         * 
+         * service.shutdown();
+         * 
+         * // Copy color map information
+         * for (int c = 0; c < in.getSizeC(); c++)
+         * out.getColorModel().setColorMap(c, in.getColorMap(c), true);
+         * 
+         * return out;
+         */
     }
 
     @Override