diff --git a/metroloSPIM/.classpath b/metroloSPIM/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..698b6a5b7244de66c980138d80559422711ac7a4
--- /dev/null
+++ b/metroloSPIM/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="var" path="ICY_JAR"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="var" path="ICY_PLUGINS/adufour/blocks/Blocks.jar"/>
+	<classpathentry kind="var" path="ICY_PLUGINS/adufour/ezplug/EzPlug.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/metroloSPIM/.gitignore b/metroloSPIM/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..be17b1e53c498ebd3551c5429e80c1c35d4dc916
--- /dev/null
+++ b/metroloSPIM/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+*.jar
\ No newline at end of file
diff --git a/metroloSPIM/.project b/metroloSPIM/.project
new file mode 100644
index 0000000000000000000000000000000000000000..4a6a4df15d4c5b109ff3a559f92ec1ca81249430
--- /dev/null
+++ b/metroloSPIM/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>metroloSPIM</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/metroloSPIM/export.jardesc b/metroloSPIM/export.jardesc
new file mode 100644
index 0000000000000000000000000000000000000000..67403f337ae1b80b9121259d170bcf1f99507692
--- /dev/null
+++ b/metroloSPIM/export.jardesc
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
+<jardesc>
+    <jar path="metroloSPIM/metroloSPIM.jar"/>
+    <options buildIfNeeded="true" compress="true" descriptionLocation="/metroloSPIM/export.jardesc" exportErrors="false" exportWarnings="true" includeDirectoryEntries="true" overwrite="true" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
+    <storedRefactorings deprecationInfo="true" structuralOnly="false"/>
+    <selectedProjects/>
+    <manifest generateManifest="true" manifestLocation="/Icy-Kernel/META-INF/MANIFEST.MF" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
+        <sealing sealJar="false">
+            <packagesToSeal/>
+            <packagesToUnSeal/>
+        </sealing>
+    </manifest>
+    <selectedElements exportClassFiles="true" exportJavaFiles="true" exportOutputFolder="false">
+        <javaElement handleIdentifier="=metroloSPIM/src"/>
+    </selectedElements>
+</jardesc>
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/ComputeQuadrants.java b/metroloSPIM/src/plugins/adufour/metrolospim/ComputeQuadrants.java
new file mode 100644
index 0000000000000000000000000000000000000000..368111e384ac78cc477f9480f81fefa06f4308ce
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/ComputeQuadrants.java
@@ -0,0 +1,180 @@
+package plugins.adufour.metrolospim;
+
+import icy.canvas.IcyCanvas;
+import icy.painter.Overlay;
+import icy.plugin.abstract_.Plugin;
+import icy.roi.ROI;
+import icy.roi.ROI2D;
+import icy.roi.ROIUtil;
+import icy.sequence.Sequence;
+import icy.util.GraphicsUtil;
+import icy.util.ShapeUtil.BooleanOperator;
+import icy.util.StringUtil;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.lang.VarBoolean;
+import plugins.adufour.vars.lang.VarDouble;
+import plugins.adufour.vars.lang.VarSequence;
+import plugins.kernel.roi.roi2d.ROI2DArea;
+import plugins.kernel.roi.roi2d.ROI2DPolygon;
+
+public class ComputeQuadrants extends Plugin implements Block
+{
+    VarSequence input            = new VarSequence("Sequence", null);
+    
+    VarBoolean  splitExistingROI = new VarBoolean("Split ROI", true);
+    
+    Overlay     overlay;
+    
+    VarDouble   north            = new VarDouble("North (%)", 0.0);
+    VarDouble   east             = new VarDouble("East (%)", 0.0);
+    VarDouble   west             = new VarDouble("West (%)", 0.0);
+    VarDouble   south            = new VarDouble("South (%)", 0.0);
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("sequence", input);
+        inputMap.add("split", splitExistingROI);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("north", north);
+        outputMap.add("east", east);
+        outputMap.add("west", west);
+        outputMap.add("south", south);
+    }
+    
+    @Override
+    public void run()
+    {
+        Sequence sequence = input.getValue();
+        
+        // create each quadrant as a polygon
+        
+        int w = sequence.getWidth();
+        int h = sequence.getHeight();
+        
+        final ROI2DPolygon[] quadrants = new ROI2DPolygon[4];
+        
+        // north
+        quadrants[0] = new ROI2DPolygon(new Point2D.Double(0, 0));
+        quadrants[0].addPoint(new Point2D.Double(w / 2.0, -0.5 + h / 2.0), true);
+        quadrants[0].addPoint(new Point2D.Double(w, 0), true);
+        
+        // west
+        quadrants[1] = new ROI2DPolygon(new Point2D.Double(0, h - 0.5));
+        quadrants[1].addPoint(new Point2D.Double(-0.5 + w / 2.0, h / 2.0), true);
+        quadrants[1].addPoint(new Point2D.Double(0, 0), true);
+        
+        // east
+        quadrants[2] = new ROI2DPolygon(new Point2D.Double(w + 0.5, 0));
+        quadrants[2].addPoint(new Point2D.Double(0.5 + w / 2.0, h / 2.0), true);
+        quadrants[2].addPoint(new Point2D.Double(w + 0.5, h + 0.5), true);
+        
+        // south
+        quadrants[3] = new ROI2DPolygon(new Point2D.Double(w + 0.5, h + 0.5));
+        quadrants[3].addPoint(new Point2D.Double(w / 2.0, 0.5 + h / 2.0), true);
+        quadrants[3].addPoint(new Point2D.Double(0, h + 0.5), true);
+        
+        for (ROI2DPolygon quadrant : quadrants)
+            quadrant.setColor(Color.blue);
+        
+        final double[] areas = new double[4];
+        
+        // merge all existing sequence ROI into a single area
+        
+        ArrayList<ROI> rois = sequence.getROIs();
+        if (rois.size() > 0)
+        {
+            ROI2D merge = (ROI2D) ROIUtil.merge(rois, BooleanOperator.OR);
+            ROI2DArea mergeArea = new ROI2DArea(merge.getBooleanMask(true));
+            
+            double totalArea = mergeArea.getNumberOfPoints();
+            
+            if (totalArea > 0)
+            {
+                // compute the surface ratio inside each quadrant
+                
+                for (int i = 0; i < 4; i++)
+                {
+                    // intersect the quadrant with the total surface
+                    areas[i] = ROIUtil.merge(Arrays.asList(quadrants[i], (ROI) mergeArea), BooleanOperator.AND).getNumberOfPoints();
+                    
+                    // make it a ratio
+                    areas[i] /= totalArea;
+                }
+                
+                for (int i = 0; i < 4; i++)
+                {
+                    // set a gradual color in "restricted" HSV space
+                    // => from blue (240', i.e. 2f/3f) to red (0', or 0f)
+                    
+                    // scale the areas to get a [2/3,0] interval
+                    double hsv = (2.0 / 3.0) - (areas[i] * 2.0 / 3.0);
+                    
+                    quadrants[i].setColor(Color.getHSBColor((float) hsv, 1f, 1f));
+                    
+                    // make it a percentage for display purposes
+                    areas[i] *= 100;
+                }
+            }
+        }
+        
+        north.setValue(areas[0]);
+        west.setValue(areas[1]);
+        east.setValue(areas[2]);
+        south.setValue(areas[3]);
+        
+        // remove the overlay if it existed
+        if (overlay != null) sequence.removeOverlay(overlay);
+        
+        sequence.addOverlay(overlay = new Overlay("Quadrants")
+        {
+            @Override
+            public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
+            {
+                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                
+                float fontSize = (float) canvas.canvasToImageLogDeltaX(30);
+                g.setFont(new Font("Trebuchet MS", Font.BOLD, 10).deriveFont(fontSize));
+                
+                double stroke = Math.max(canvas.canvasToImageLogDeltaX(2), canvas.canvasToImageLogDeltaY(2));
+                
+                g.setStroke(new BasicStroke((float) stroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+                
+                for (int i = 0; i < 4; i++)
+                {
+                    ROI2DPolygon quadrant = quadrants[i];
+                    
+                    Rectangle bounds = quadrant.getBounds();
+                    String text = StringUtil.toString(areas[i], 2) + "%";
+                    Dimension size = GraphicsUtil.getHintSize(g, text);
+                    
+                    int x = (int) bounds.getCenterX() - size.width / 2;
+                    int y = (int) bounds.getCenterY() - size.height / 2;
+                    
+                    g.setColor(quadrant.getColor());
+                    
+                    g.draw(quadrant);
+                    
+                    GraphicsUtil.drawHint(g, text, x, y, quadrant.getColor(), quadrant.getColor());
+                }
+            }
+        });
+    }
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/Create3DRectangleROI.java b/metroloSPIM/src/plugins/adufour/metrolospim/Create3DRectangleROI.java
new file mode 100644
index 0000000000000000000000000000000000000000..ccf98b1007b0f882c550eb44ac442a5a133a2c10
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/Create3DRectangleROI.java
@@ -0,0 +1,83 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.type.point.Point3D;
+import plugins.adufour.blocks.tools.roi.ROIBlock;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.roi.ROI3DRectangle;
+import plugins.adufour.vars.lang.VarDoubleArrayNative;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.adufour.vars.util.VarException;
+
+public class Create3DRectangleROI extends Plugin implements ROIBlock
+{
+    VarDoubleArrayNative xCenter = new VarDoubleArrayNative("Centers (X)", null);
+    
+    VarDoubleArrayNative yCenter = new VarDoubleArrayNative("Centers (Y)", null);
+    
+    VarDoubleArrayNative zCenter = new VarDoubleArrayNative("Centers (Z)", null);
+    
+    VarDoubleArrayNative xSize   = new VarDoubleArrayNative("Width(s)", null);
+    
+    VarDoubleArrayNative ySize   = new VarDoubleArrayNative("Height(s)", null);
+    
+    VarDoubleArrayNative zSize   = new VarDoubleArrayNative("Depth(s)", null);
+    
+    VarROIArray          roi     = new VarROIArray("List of ROI");
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("center (X)", xCenter);
+        inputMap.add("center (Y)", yCenter);
+        inputMap.add("center (Z)", zCenter);
+        inputMap.add("sizes (X)", xSize);
+        inputMap.add("sizes (Y)", ySize);
+        inputMap.add("sizes (Z)", zSize);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add(roi);
+    }
+    
+    @Override
+    public void run()
+    {
+        int n = xCenter.size();
+        if (n != yCenter.size() || n != zCenter.size()) throw new VarException("[Create 3D Rectangle ROI] The lists of centers don't have the same number of elements");
+        
+        int nSize = xSize.size();
+        if (nSize != ySize.size() || nSize != zSize.size()) throw new VarException("[Create 3D Rectangle ROI] The lists of sizes don't have the same number of elements");
+        
+        if (nSize != 1 && nSize != n) throw new VarException("[Create 3D Rectangle ROI] The number of sizes must be equal to 1 or the number of centers");
+        
+        for (int i = 0; i < n; i++)
+        {
+            // get the dimensions
+            double sizeX, sizeY, sizeZ;
+            
+            if (nSize == 1)
+            {
+                sizeX = xSize.getValue()[0];
+                sizeY = ySize.getValue()[0];
+                sizeZ = zSize.getValue()[0];
+            }
+            else
+            {
+                sizeX = xSize.getValue()[i];
+                sizeY = ySize.getValue()[i];
+                sizeZ = zSize.getValue()[i];
+            }
+            
+            // create the ROI
+            
+            double minX = xCenter.getValue()[i] - sizeX / 2;
+            double minY = yCenter.getValue()[i] - sizeY / 2;
+            double minZ = zCenter.getValue()[i] - sizeZ / 2;
+            
+            roi.add(new ROI3DRectangle(new Point3D.Double(minX, minY, minZ), new Point3D.Double(sizeX, sizeY, sizeZ)));
+        }
+    }
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/CreatedScaledRectangleROI.java b/metroloSPIM/src/plugins/adufour/metrolospim/CreatedScaledRectangleROI.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a089cfadd68cc4b603a0fa536efc334bbdc4c9d
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/CreatedScaledRectangleROI.java
@@ -0,0 +1,84 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.roi.ROI;
+import icy.roi.ROI2D;
+import icy.roi.ROI3D;
+import icy.type.point.Point3D;
+import icy.type.rectangle.Rectangle5D;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import plugins.adufour.blocks.tools.roi.ROIBlock;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.roi.ROI3DRectangle;
+import plugins.adufour.vars.lang.VarDouble;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.kernel.roi.roi2d.ROI2DRectangle;
+import plugins.kernel.roi.roi3d.ROI3DStack;
+
+public class CreatedScaledRectangleROI extends Plugin implements ROIBlock
+{
+    VarROIArray inROI       = new VarROIArray("list of ROI");
+    VarDouble   scaleFactor = new VarDouble("scale", 2);
+    VarROIArray rescaledROI = new VarROIArray("list of rescaled ROI");
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("input ROI", inROI);
+        inputMap.add("scale factor", scaleFactor);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("output ROI", rescaledROI);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public void run()
+    {
+        double scale = scaleFactor.getValue();
+        
+        for (ROI roi : inROI.getValue(true))
+        {
+            if (roi instanceof ROI2D)
+            {
+                Rectangle2D r = ((ROI2D) roi).getBounds2D();
+                Point2D.Double topLeft = new Point2D.Double(r.getMinX() * scale, r.getMinY() * scale);
+                Point2D.Double bottomRight = new Point2D.Double(r.getMaxX() * scale, r.getMaxY() * scale);
+                ROI2DRectangle rect = new ROI2DRectangle(topLeft, bottomRight);
+                rescaledROI.add(rect);
+            }
+            if (roi instanceof ROI3D)
+            {
+                Rectangle5D r5 = roi.getBounds5D();
+                
+                if (roi instanceof ROI3DStack)
+                {
+                    // recompute bounds (and headshoot Stephane)
+                    boolean firstSlice = true;
+                    for (ROI2D slice : (ROI3DStack<ROI2D>) roi)
+                    {
+                        if (firstSlice)
+                        {
+                            r5 = slice.computeBounds5D();
+                            firstSlice = false;
+                        }
+                        else r5.add(slice.getBounds5D());
+                    }
+                }
+                
+                Point3D.Double topLeftUp = new Point3D.Double(r5.getX() * scale, r5.getY() * scale, r5.getZ() * scale);
+                Point3D.Double bottomRightDown = new Point3D.Double(r5.getSizeX() * scale, r5.getSizeY() * scale, r5.getSizeZ() * scale);
+                ROI3DRectangle rect = new ROI3DRectangle(topLeftUp, bottomRightDown);
+                rescaledROI.add(rect);
+            }
+            else throw new UnsupportedOperationException("Warning: ROI of type " + roi.getClassName() + " are not currently supported");
+        }
+    }
+    
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/ExtractEquator.java b/metroloSPIM/src/plugins/adufour/metrolospim/ExtractEquator.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa3006e6e1679c510b888506d668c1eaee393fbc
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/ExtractEquator.java
@@ -0,0 +1,51 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.sequence.Sequence;
+import icy.sequence.SequenceUtil;
+
+import java.awt.Rectangle;
+
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.gui.model.IntegerRangeModel;
+import plugins.adufour.vars.lang.VarInteger;
+import plugins.adufour.vars.lang.VarSequence;
+import plugins.kernel.roi.roi2d.ROI2DRectangle;
+
+public class ExtractEquator extends Plugin implements Block
+{
+    VarSequence seq       = new VarSequence("input", null);
+    VarInteger  thickness = new VarInteger("thickness (% of total height)", 20);
+    VarSequence equator   = new VarSequence("equator", null);
+    
+    @Override
+    public void run()
+    {
+        Sequence in = seq.getValue(true);
+        
+        int newHeight = (int) (in.getHeight() / 100.0 * thickness.getValue());
+        
+        int centerY = in.getSizeY() / 2;
+        int newY = centerY - newHeight / 2;
+        
+        ROI2DRectangle rectangle = new ROI2DRectangle(new Rectangle(0, newY, in.getSizeX(), newHeight));
+        
+        equator.setValue(SequenceUtil.getSubSequence(in, rectangle));
+    }
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("input sequence", seq);
+        thickness.setDefaultEditorModel(new IntegerRangeModel(20, 1, 100, 1));
+        inputMap.add("thickness (% of height)", thickness);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("output equator", equator);
+    }
+    
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/FitCircle.java b/metroloSPIM/src/plugins/adufour/metrolospim/FitCircle.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f09dbd8fa1d6f38abd9090f80616c50fdaa2b39
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/FitCircle.java
@@ -0,0 +1,71 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.roi.ROI;
+import icy.roi.ROI2D;
+
+import java.awt.Point;
+import java.util.Arrays;
+
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.kernel.roi.roi2d.ROI2DArea;
+import plugins.kernel.roi.roi2d.ROI2DEllipse;
+import plugins.kernel.roi.roi2d.ROI2DShape;
+
+/**
+ * @author Alexandre Dufour
+ */
+public class FitCircle extends Plugin implements Block
+{
+	VarROIArray roiArray = new VarROIArray("List of ROI");
+	VarROIArray roiOUT = new VarROIArray("List of circles");
+
+	/**
+	 * @see java.lang.Runnable#run()
+	 */
+	@Override
+	public void run()
+	{
+		for (ROI roi : roiArray.getValue())
+		{
+			if (roi instanceof ROI2D)
+			{
+				ROI2DEllipse circle = new ROI2DEllipse();
+
+				if (roi instanceof ROI2DArea)
+				{
+					ROI2DArea area = (ROI2DArea) roi;
+					Point[] points = area.getBooleanMask(true).getContourPoints();
+					circle.setToFitCircle(Arrays.asList(points));
+				}
+				else if (roi instanceof ROI2DShape)
+				{
+					ROI2DShape shape = (ROI2DShape) roi;
+					circle.setToFitCircle(shape.getPoints());
+				}
+
+				roiOUT.add(circle);
+			}
+		}
+	}
+
+	/**
+	 * @see plugins.adufour.blocks.lang.Block#declareInput(plugins.adufour.blocks.util.VarList)
+	 */
+	@Override
+	public void declareInput(VarList inputMap)
+	{
+		inputMap.add("input ROI array", roiArray);
+	}
+
+	/**
+	 * @see plugins.adufour.blocks.lang.Block#declareOutput(plugins.adufour.blocks.util.VarList)
+	 */
+	@Override
+	public void declareOutput(VarList outputMap)
+	{
+		outputMap.add("output ROI array", roiOUT);
+	}
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/GetROIMassCenter.java b/metroloSPIM/src/plugins/adufour/metrolospim/GetROIMassCenter.java
new file mode 100644
index 0000000000000000000000000000000000000000..83a0be6479ea334401f11985a3399a26f095e195
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/GetROIMassCenter.java
@@ -0,0 +1,74 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.roi.ROI;
+import icy.roi.ROI2D;
+import icy.type.rectangle.Rectangle5D;
+import plugins.adufour.blocks.tools.roi.ROIBlock;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.lang.VarDoubleArrayNative;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.kernel.roi.roi3d.ROI3DStack;
+
+public class GetROIMassCenter extends Plugin implements ROIBlock
+{
+    VarROIArray          roiArray = new VarROIArray("List of ROI");
+    
+    VarDoubleArrayNative xCenter  = new VarDoubleArrayNative("Centers (X)", null);
+    
+    VarDoubleArrayNative yCenter  = new VarDoubleArrayNative("Centers (Y)", null);
+    
+    VarDoubleArrayNative zCenter  = new VarDoubleArrayNative("Centers (Z)", null);
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("inputROI", roiArray);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("center (X)", xCenter);
+        outputMap.add("center (Y)", yCenter);
+        outputMap.add("center (Z)", zCenter);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public void run()
+    {
+        int length = roiArray.size();
+        
+        xCenter.setValue(new double[length]);
+        yCenter.setValue(new double[length]);
+        zCenter.setValue(new double[length]);
+        
+        int cpt = -1;
+        for (ROI roi : roiArray.getValue())
+        {
+            cpt++;
+            
+            Rectangle5D r5 = roi.computeBounds5D();
+            
+            if (roi instanceof ROI3DStack)
+            {
+                boolean firstSlice = true;
+                
+                for (ROI2D slice : (ROI3DStack<ROI2D>) roi)
+                {
+                    if (firstSlice)
+                    {
+                        r5 = slice.computeBounds5D();
+                        firstSlice = false;
+                    }
+                    else r5.add(slice.getBounds5D());
+                }
+            }
+            
+            xCenter.getValue()[cpt] = r5.getCenterX();
+            yCenter.getValue()[cpt] = r5.getCenterY();
+            zCenter.getValue()[cpt] = r5.getCenterZ();
+        }
+    }
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/MetroloSPIM.java b/metroloSPIM/src/plugins/adufour/metrolospim/MetroloSPIM.java
new file mode 100644
index 0000000000000000000000000000000000000000..2291647e7d791ef927cd6b03a873fdcf7e89aab4
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/MetroloSPIM.java
@@ -0,0 +1,9 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.plugin.interface_.PluginLibrary;
+
+public class MetroloSPIM extends Plugin implements PluginLibrary
+{
+
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/OrthogonalRotation.java b/metroloSPIM/src/plugins/adufour/metrolospim/OrthogonalRotation.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e1abae19e80cbc05657213420e070ab00755450
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/OrthogonalRotation.java
@@ -0,0 +1,173 @@
+package plugins.adufour.metrolospim;
+
+import icy.image.IcyBufferedImage;
+import icy.plugin.abstract_.Plugin;
+import icy.sequence.Sequence;
+import icy.type.DataType;
+
+import java.lang.reflect.Array;
+
+import plugins.adufour.blocks.tools.sequence.SequenceBlock;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.lang.VarEnum;
+import plugins.adufour.vars.lang.VarSequence;
+
+public class OrthogonalRotation extends Plugin implements SequenceBlock
+{
+    public enum Orientation
+    {
+        X_becomes_Y, Y_becomes_X, X_becomes_Z, Y_becomes_Z, Z_becomes_X, Z_becomes_Y
+    }
+    
+    VarSequence          input       = new VarSequence("Sequence", null);
+    
+    VarEnum<Orientation> orientation = new VarEnum<OrthogonalRotation.Orientation>("Orienation", Orientation.Z_becomes_Y);
+    
+    VarSequence          output      = new VarSequence("Rotated sequence", null);
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("input sequence", input);
+        inputMap.add("orientation", orientation);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("output sequence", output);
+    }
+    
+    @Override
+    public void run()
+    {
+        Sequence in = input.getValue(true);
+        Orientation or = orientation.getValue(true);
+        Sequence out = null;
+        
+        int sizeX = in.getSizeX();
+        int sizeY = in.getSizeY();
+        int sizeXY = sizeX * sizeY;
+        int sizeZ = in.getSizeZ();
+        int sizeC = in.getSizeC();
+        int sizeT = in.getSizeT();
+        DataType dataType = in.getDataType_();
+        
+        int startOffset = 0;
+        int sign = 1;
+        
+        switch (or)
+        {
+            case X_becomes_Y:
+            case Y_becomes_X:
+                
+                startOffset = (or == Orientation.X_becomes_Y ? sizeXY - sizeY : sizeY - 1);
+                sign = (or == Orientation.X_becomes_Y ? 1 : -1);
+                
+                // out has inverted XY dimensions
+                out = new Sequence();
+                for (int t = 0; t < sizeT; t++)
+                    for (int z = 0; z < sizeZ; z++)
+                        out.setImage(t, z, new IcyBufferedImage(sizeY, sizeX, sizeC, dataType));
+                
+                for (int t = 0; t < sizeT; t++)
+                    for (int c = 0; c < sizeC; c++)
+                        for (int z = 0; z < sizeZ; z++)
+                        {
+                            final Object in_XY = in.getDataXY(t, z, c);
+                            final Object out_XY = out.getDataXY(t, z, c);
+                            
+                            // browse each line of the input and store in the output accordingly
+                            
+                            for (int j = 0; j < sizeY; j++)
+                            {
+                                int inOffset = j * sizeX;
+                                int outOffset = startOffset + j * sign;
+                                
+                                for (int i = 0; i < sizeX; i++, inOffset++, outOffset -= sizeY * sign)
+                                {
+                                    Array.set(out_XY, outOffset, Array.get(in_XY, inOffset));
+                                }
+                            }
+                        }
+            break;
+            
+            case X_becomes_Z:
+            case Z_becomes_X:
+                
+                startOffset = (or == Orientation.X_becomes_Z ? sizeZ - 1 : 0);
+                sign = (or == Orientation.X_becomes_Z ? -1 : 1);
+                
+                // out has inverted XZ dimensions
+                out = new Sequence();
+                for (int t = 0; t < sizeT; t++)
+                    for (int x = 0; x < sizeX; x++)
+                        out.setImage(t, x, new IcyBufferedImage(sizeZ, sizeY, sizeC, dataType));
+                
+                for (int t = 0; t < sizeT; t++)
+                    for (int c = 0; c < sizeC; c++)
+                    {
+                        final Object out_Z_XY = out.getDataXYZ(t, c);
+                        
+                        for (int z = 0; z < sizeZ; z++)
+                        {
+                            final Object in_XY = in.getDataXY(t, z, c);
+                            
+                            // browse each line of the input and store in the output accordingly
+                            
+                            for (int j = 0; j < sizeY; j++)
+                            {
+                                int inOffset = j * sizeX;
+                                int outOffset = startOffset + j * sizeZ + z * sign;
+                                
+                                for (int i = 0; i < sizeX; i++, inOffset++)
+                                {
+                                    Object outSlice = Array.get(out_Z_XY, i);
+                                    Array.set(outSlice, outOffset, Array.get(in_XY, inOffset));
+                                }
+                            }
+                        }
+                    }
+            break;
+            
+            case Y_becomes_Z:
+            case Z_becomes_Y:
+                
+                startOffset = (or == Orientation.Z_becomes_Y ? 0 : sizeY - 1);
+                sign = (or == Orientation.Z_becomes_Y ? 1 : -1);
+                
+                // out has inverted YZ dimensions
+                out = new Sequence();
+                for (int t = 0; t < sizeT; t++)
+                    for (int y = 0; y < sizeY; y++)
+                        out.setImage(t, y, new IcyBufferedImage(sizeX, sizeZ, sizeC, dataType));
+                
+                for (int t = 0; t < sizeT; t++)
+                    for (int c = 0; c < sizeC; c++)
+                    {
+                        final Object out_Z_XY = out.getDataXYZ(t, c);
+                        
+                        for (int z = 0; z < sizeZ; z++)
+                        {
+                            final Object in_XY = in.getDataXY(t, z, c);
+                            
+                            int outOffset = (sizeZ - z - 1) * sizeX;
+                            
+                            // browse each line of the input and store in the output accordingly
+                            for (int j = 0; j < sizeY; j++)
+                            {
+                                int inOffset = j * sizeX;
+                                Object outSlice = Array.get(out_Z_XY, startOffset + j * sign);
+                                
+                                System.arraycopy(in_XY, inOffset, outSlice, outOffset, sizeX);
+                            }
+                        }
+                    }
+            
+            break;
+        }
+        
+        out.dataChanged();
+        output.setValue(out);
+    }
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/ROI2DLineProfile.java b/metroloSPIM/src/plugins/adufour/metrolospim/ROI2DLineProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a371314565cc59bf59f3ed43bcb949719af2272
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/ROI2DLineProfile.java
@@ -0,0 +1,47 @@
+package plugins.adufour.metrolospim;
+
+import icy.image.IcyBufferedImage;
+import icy.plugin.abstract_.Plugin;
+import icy.type.collection.array.Array1DUtil;
+
+import java.awt.geom.Point2D;
+
+public class ROI2DLineProfile extends Plugin
+{
+    public static double[][] getSegmentProfile(Point2D p1, Point2D p2, IcyBufferedImage image)
+    {
+        int distance = (int) p1.distance(p2);
+        
+        double vx = (p2.getX() - p1.getX()) / distance;
+        double vy = (p2.getY() - p1.getY()) / distance;
+        
+        int nbComponent = image.getSizeC();
+        double[][] data = new double[nbComponent][distance];
+        
+        double x = p1.getX();
+        double y = p1.getY();
+        
+        for (int i = 0; i < distance; i++)
+        {
+            if (image.isInside((int) x, (int) y))
+            {
+                for (int component = 0; component < nbComponent; component++)
+                {
+                    data[component][i] = Array1DUtil.getValue(image.getDataXY(component), image.getOffset((int) x, (int) y), image.isSignedDataType());
+                }
+            }
+            else
+            {
+                for (int component = 0; component < nbComponent; component++)
+                {
+                    data[component][i] = 0.0D;
+                }
+            }
+            
+            x += vx;
+            y += vy;
+        }
+        
+        return data;
+    }
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/RescaleSequence.java b/metroloSPIM/src/plugins/adufour/metrolospim/RescaleSequence.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a1d56187eefd8c4de3b563f8b0bcfdbad054561
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/RescaleSequence.java
@@ -0,0 +1,158 @@
+package plugins.adufour.metrolospim;
+
+import icy.image.IcyBufferedImage;
+import icy.image.IcyBufferedImageUtil;
+import icy.image.IcyBufferedImageUtil.FilterType;
+import icy.plugin.abstract_.Plugin;
+import icy.sequence.Sequence;
+import icy.system.SystemUtil;
+import icy.type.DataType;
+import icy.type.collection.array.Array1DUtil;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import plugins.adufour.blocks.tools.sequence.SequenceBlock;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.gui.model.DoubleRangeModel;
+import plugins.adufour.vars.lang.VarDouble;
+import plugins.adufour.vars.lang.VarSequence;
+
+public class RescaleSequence extends Plugin implements SequenceBlock
+{
+    VarSequence varSeq         = new VarSequence("Sequence", null);
+    VarDouble   varScale       = new VarDouble("Scale factor", 0.5);
+    
+    VarSequence varSeqRescaled = new VarSequence("Rescaled sequence", null);
+    
+    @Override
+    public void run()
+    {
+        Sequence in = varSeq.getValue(true);
+        final Sequence out = new Sequence("Rescaled " + in.getName());
+        
+        final int newWidth = (int) (in.getSizeX() * varScale.getValue());
+        final int newHeight = (int) (in.getSizeY() * varScale.getValue());
+        final int newDepth = (int) (in.getSizeZ() * varScale.getValue());
+        
+        final double scale = varScale.getValue();
+        
+        final boolean downScale = (scale < 1.0);
+        
+        final double gridStep = 1 / scale;
+        
+        final int sizeT = in.getSizeT();
+        final int sizeZ = in.getSizeZ();
+        final int sizeC = in.getSizeC();
+        final int sizeX = in.getSizeX();
+        final DataType dataType = in.getDataType_();
+        
+        if (downScale)
+        {
+            for (int t = 0; t < sizeT; t++)
+                for (int z = 0; z < newDepth; z++)
+                {
+                    out.setImage(t, z, new IcyBufferedImage(newWidth, newHeight, sizeC, dataType));
+                }
+        }
+        
+        ExecutorService service = Executors.newFixedThreadPool(SystemUtil.getAvailableProcessors());
+        
+        ArrayList<Callable<Object>> tasks = new ArrayList<Callable<Object>>(sizeZ * sizeT);
+        
+        for (int t = 0; t < sizeT; t++)
+        {
+            for (int k = 0; k < newDepth; k++)
+            {
+                int slice = (int) (k * gridStep);
+                
+                if (downScale)
+                {
+                    // optimize the process manually
+                    
+                    final Object _in = in.getDataXYC(t, slice);
+                    final Object _out = out.getDataXYC(t, k);
+                    
+                    tasks.add(new Callable<Object>()
+                    {
+                        @Override
+                        public Object call()
+                        {
+                            for (int c = 0; c < sizeC; c++)
+                            {
+                                Object _inXY = Array.get(_in, c);
+                                Object _outXY = Array.get(_out, c);
+                                double y = gridStep * 0.5;
+                                
+                                int off = 0;
+                                for (int j = 0; j < newHeight; j++)
+                                {
+                                    double x = gridStep * 0.5;
+                                    
+                                    for (int i = 0; i < newWidth; i++, off++)
+                                    {
+                                        double value = Array1DUtil.getValue(_inXY, (int) (x + y * sizeX), dataType);
+                                        Array1DUtil.setValue(_outXY, off, dataType, value);
+                                        x += gridStep;
+                                        
+                                    }
+                                    
+                                    y += gridStep;
+                                }
+                            }
+                            return null;
+                        }
+                    });
+                }
+                else
+                {
+                    final IcyBufferedImage inSlice = in.getImage(t, slice);
+                    final int outT = t;
+                    final int outZ = k;
+                    
+                    tasks.add(new Callable<Object>()
+                    {
+                        @Override
+                        public Object call()
+                        {
+                            out.setImage(outT, outZ, IcyBufferedImageUtil.scale(inSlice, newWidth, newHeight, FilterType.NEAREST));
+                            return null;
+                        }
+                    });
+                }
+            }
+        }
+        
+        try
+        {
+            service.invokeAll(tasks);
+        }
+        catch (InterruptedException e)
+        {
+        }
+        finally
+        {
+            service.shutdownNow();
+        }
+        
+        varSeqRescaled.setValue(out);
+    }
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("input sequence", varSeq);
+        varScale.setDefaultEditorModel(new DoubleRangeModel(0.5, 0.001, 1000.0, 0.001));
+        inputMap.add("scale factor", varScale);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("output sequence", varSeqRescaled);
+    }
+    
+}
diff --git a/metroloSPIM/src/plugins/adufour/metrolospim/Sum.java b/metroloSPIM/src/plugins/adufour/metrolospim/Sum.java
new file mode 100644
index 0000000000000000000000000000000000000000..a29aa5767fbc89c7692513a1ad4161d7813983b1
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/metrolospim/Sum.java
@@ -0,0 +1,42 @@
+package plugins.adufour.metrolospim;
+
+import icy.plugin.abstract_.Plugin;
+import icy.sequence.SequenceDataIterator;
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.vars.lang.VarDouble;
+import plugins.adufour.vars.lang.VarSequence;
+
+public class Sum extends Plugin implements Block
+{
+    VarSequence seq = new VarSequence("sequence", null);
+    VarDouble   sum = new VarDouble("sum", 0);
+    
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add(seq);
+    }
+    
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add(sum);
+    }
+    
+    @Override
+    public void run()
+    {
+        double d = 0;
+        
+        SequenceDataIterator it = new SequenceDataIterator(seq.getValue(true));
+        while (!it.done())
+        {
+            d += it.get();
+            it.next();
+        }
+        
+        sum.setValue(d);
+    }
+    
+}
diff --git a/metroloSPIM/src/plugins/adufour/roi/ROI3DRectangle.java b/metroloSPIM/src/plugins/adufour/roi/ROI3DRectangle.java
new file mode 100644
index 0000000000000000000000000000000000000000..40c6233259719f07df5c1b6693881f0ee1d3a34c
--- /dev/null
+++ b/metroloSPIM/src/plugins/adufour/roi/ROI3DRectangle.java
@@ -0,0 +1,128 @@
+package plugins.adufour.roi;
+
+import icy.canvas.Canvas2D;
+import icy.canvas.IcyCanvas;
+import icy.roi.ROI3D;
+import icy.sequence.Sequence;
+import icy.type.point.Point3D;
+import icy.type.rectangle.Rectangle3D;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+
+import plugins.kernel.roi.roi2d.ROI2DRectangle;
+
+public class ROI3DRectangle extends ROI3D
+{
+    private final Rectangle3D    bounds;
+    
+    private final ROI2DRectangle rectangleROI = new ROI2DRectangle();
+    
+    public ROI3DRectangle()
+    {
+        bounds = new Rectangle3D.Double(0, 0, 0, 0, 0, 0);
+    }
+    
+    public ROI3DRectangle(Point3D topLeftUp, Point3D bottomRightDown)
+    {
+        this(new Rectangle3D.Double(topLeftUp.getX(), topLeftUp.getY(), topLeftUp.getZ(), bottomRightDown.getX(), bottomRightDown.getY(), bottomRightDown.getZ()));
+    }
+    
+    public ROI3DRectangle(Rectangle3D bounds)
+    {
+        this.bounds = bounds;
+        roiChanged();
+    }
+    
+    /**
+     * @return a copy of the bounds of this ROI
+     */
+    @Override
+    public Rectangle3D computeBounds3D()
+    {
+        return new Rectangle3D.Double(bounds.getX(), bounds.getY(), bounds.getZ(), bounds.getSizeX(), bounds.getSizeY(), bounds.getSizeZ());
+    }
+    
+    @Override
+    protected ROIPainter createPainter()
+    {
+        return new ROI3DRectanglePainter();
+    }
+    
+    @Override
+    public boolean contains(double x, double y, double z)
+    {
+        return bounds.contains(x, y, z);
+    }
+    
+    @Override
+    public boolean contains(double x, double y, double z, double sizeX, double sizeY, double sizeZ)
+    {
+        return bounds.contains(x, y, z, sizeX, sizeY, sizeZ);
+    }
+
+    @Override
+    public boolean hasSelectedPoint()
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean intersects(double x, double y, double z, double sizeX, double sizeY, double sizeZ)
+    {
+        return bounds.intersects(x, y, z, sizeX, sizeY, sizeZ);
+    }
+    
+    @Override
+    public double getPerimeter()
+    {
+        return bounds.getSizeX() * bounds.getSizeY() * 2 + bounds.getSizeX() * bounds.getSizeZ() * 2 + bounds.getSizeY() * bounds.getSizeZ() * 2;
+    }
+    
+    @Override
+    public double getVolume()
+    {
+        return bounds.getSizeX() * bounds.getSizeY() * bounds.getSizeZ();
+    }
+    
+    @Override
+    public void roiChanged()
+    {
+        super.roiChanged();
+        rectangleROI.setBounds2D(new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getSizeX(), bounds.getSizeY()));
+        rectangleROI.setZ((int) bounds.getZ());
+    }
+    
+    public class ROI3DRectanglePainter extends ROIPainter
+    {
+        @Override
+        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
+        {
+            super.paint(g, sequence, canvas);
+            
+            if (canvas instanceof Canvas2D)
+            {
+                ROIPainter overlay = rectangleROI.getOverlay();
+//                if (!overlay.isAttached(sequence)) overlay.
+                
+                int z = canvas.getPositionZ();
+                
+                if (z >= bounds.getZ() && z < bounds.getZ() + bounds.getSizeZ())
+                {
+                    rectangleROI.setZ(z);
+                    overlay.paint(g, sequence, canvas);
+                }
+            }
+            else if (canvas.getClass().getSimpleName().equalsIgnoreCase("OrthoCanvas"))
+            {
+                
+            }
+            else
+            {
+//                rectangleROI.getPainter().detachFrom(sequence);
+                System.err.println("Unsupported canvas: " + canvas.getClass().getSimpleName());
+            }
+        }
+    }
+}