diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3d47f986c41db29ec6dc0d5036bf760b3a1cf366
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea/
+target/
+.settings/
+*.iml
+.project
+.classpath
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3932634b72ee13bfc34241cf35223a7458d513a8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- Inherited Icy Parent POM -->
+    <parent>
+        <groupId>org.bioimageanalysis.icy</groupId>
+        <artifactId>parent-pom-plugin</artifactId>
+        <version>1.0.3</version>
+    </parent>
+
+    <!-- Project Information -->
+    <artifactId>msd-track-processor</artifactId>
+    <version>1.1.1</version>
+
+    <packaging>jar</packaging>
+
+    <name>MSD Track Processor</name>
+    <description>Track Processor computing the MSD (Mean Square Displacement).</description>
+    <url>http://icy.bioimageanalysis.org/plugin/track-processor-msd/</url>
+    <inceptionYear>2020</inceptionYear>
+
+    <organization>
+        <name>Institut Pasteur</name>
+        <url>https://pasteur.fr</url>
+    </organization>
+
+    <licenses>
+        <license>
+            <name>GNU GPLv3</name>
+            <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <id>sdallongeville</id>
+            <name>Stéphane Dallongeville</name>
+            <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url>
+            <roles>
+                <role>founder</role>
+                <role>lead</role>
+                <role>architect</role>
+                <role>developer</role>
+                <role>debugger</role>
+                <role>tester</role>
+                <role>maintainer</role>
+                <role>support</role>
+            </roles>
+        </developer>
+    </developers>
+
+    <!-- Project properties -->
+    <properties>
+
+    </properties>
+
+    <!-- Project build configuration -->
+    <build>
+
+    </build>
+
+    <!-- List of project's dependencies -->
+    <dependencies>
+        <!-- The core of Icy -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>icy-kernel</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>jfreechart</artifactId>
+            <version>1.5.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>spot-detection-utilities</artifactId>
+            <version>1.1.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>distance-profiler</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+    </dependencies>
+
+    <!-- Icy Maven repository (to find parent POM) -->
+    <repositories>
+        <repository>
+            <id>icy</id>
+            <name>Icy's Nexus</name>
+            <url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+        </repository>
+    </repositories>
+</project>
diff --git a/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java b/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java
new file mode 100644
index 0000000000000000000000000000000000000000..727c7f618d5dce1e690aa1e78ec6cae93e0b97e2
--- /dev/null
+++ b/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java
@@ -0,0 +1,532 @@
+package plugins.fab.trackmanager.processors;
+
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import icy.canvas.IcyCanvas;
+import icy.gui.util.GuiUtil;
+import icy.painter.Painter;
+import icy.preferences.GeneralPreferences;
+import icy.sequence.Sequence;
+import icy.util.StringUtil;
+import icy.util.XLSUtil;
+import jxl.write.WritableSheet;
+import jxl.write.WritableWorkbook;
+import jxl.write.WriteException;
+import plugins.fab.trackmanager.PluginTrackManagerProcessor;
+import plugins.fab.trackmanager.TrackSegment;
+import plugins.nchenouard.spot.Detection;
+
+/**
+ * @author Fabrice de Chaumont
+ */
+public class TrackProcessorMSD extends PluginTrackManagerProcessor implements ActionListener, Painter
+{
+    JFreeChart chart;
+    JCheckBox displayLegendCheckBox = new JCheckBox("Display legend.", false);
+    JCheckBox displayGraphInSequenceCheckBox = new JCheckBox("Display graph on sequence.", false);
+    JButton useRoiAsBoundsForChartButton = new JButton("place graph in ROI #1");
+    JCheckBox forceAllSequenceGraphWidthCheckBox = new JCheckBox("Force graph width.", false);
+    JCheckBox useRealScalesBox = new JCheckBox("use real scales", false);
+    JPanel chartpanel = new JPanel();
+    JTextField scaleTextField = new JTextField("1.0");
+    JButton exportButton = new JButton("export to console");
+    JButton exportExcelButton = new JButton("export to excel");
+
+    public TrackProcessorMSD()
+    {
+        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+        chartpanel.add(new ChartPanel(chart, 500, 300, 500, 300, 500, 300, false, false, true, true, true, true));
+        panel.add(GuiUtil.besidesPanel(chartpanel));
+        panel.add(GuiUtil.besidesPanel(useRealScalesBox));
+        useRealScalesBox.addActionListener(new ActionListener()
+        {
+            @Override
+            public void actionPerformed(ActionEvent e)
+            {
+                Compute();
+            }
+        });
+        panel.add(GuiUtil.besidesPanel(displayLegendCheckBox, forceAllSequenceGraphWidthCheckBox));
+        // panel.add( GuiUtil.besidesPanel( displayGraphInSequenceCheckBox , useRoiAsBoundsForChartButton ) );
+        // panel.add( GuiUtil.besidesPanel( new JLabel("Scale:") , scaleTextField ) );
+        exportButton.addActionListener(this);
+        exportExcelButton.addActionListener(this);
+        panel.add(GuiUtil.besidesPanel(exportButton, exportExcelButton));
+        useRoiAsBoundsForChartButton.addActionListener(this);
+        displayGraphInSequenceCheckBox.addActionListener(this);
+        displayLegendCheckBox.addActionListener(this);
+        forceAllSequenceGraphWidthCheckBox.addActionListener(this);
+        setName("Mean square displacement");
+        // trackPool.getDisplaySequence().addPainter( this );
+    }
+
+    @Override
+    public void Compute()
+    {
+        if (!super.isEnabled())
+            return;
+
+        XYSeriesCollection xyDataset = new XYSeriesCollection();
+
+        final Sequence seq = trackPool.getDisplaySequence();
+        final double tScale = (seq != null) ? seq.getTimeInterval() : 1d;
+
+        if (useRealScalesBox.isSelected())
+        {
+            for (TrackSegment ts : trackPool.getTrackSegmentList())
+            {
+                XYSeries series = new XYSeries("Track " + trackPool.getTrackIndex(ts));
+
+                if (ts.isAllDetectionEnabled())
+                {
+                    double[] msd = scaledMeanSquaredDisplacement(seq, ts);
+
+                    for (int i = 0; i < msd.length; i++)
+                        series.add(i * tScale, msd[i]);
+
+                    xyDataset.addSeries(series);
+                }
+            }
+        }
+        else
+        {
+            for (TrackSegment ts : trackPool.getTrackSegmentList())
+            {
+                XYSeries series = new XYSeries("Track " + trackPool.getTrackIndex(ts));
+
+                if (ts.isAllDetectionEnabled())
+                {
+                    double[] msd = scaledMeanSquaredDisplacement(null, ts);
+
+                    for (int i = 0; i < msd.length; i++)
+                        series.add(i, msd[i]);
+
+                    xyDataset.addSeries(series);
+                }
+            }
+        }
+        String TitleString = "";
+        String TitleString2 = "";
+        String TitleString3 = "";
+
+        if (displayLegendCheckBox.isSelected())
+        {
+            if (useRealScalesBox.isSelected())
+            {
+                TitleString = "Mean Square Displacement";
+                TitleString2 = "Delta (s)";
+                TitleString3 = "MSD (um^2)";
+            }
+            else
+            {
+                TitleString = "Mean Square Displacement";
+                TitleString2 = "Delta (frame)";
+                TitleString3 = "MSD (pixel^2)";
+            }
+        }
+
+        chart = ChartFactory.createXYLineChart(TitleString, // chart title
+                TitleString2, // x axis label
+                TitleString3, // y axis label
+                xyDataset, // data
+                PlotOrientation.VERTICAL, displayLegendCheckBox.isSelected(), // include legend
+                true, // tooltips
+                false // urls
+        );
+
+        if (forceAllSequenceGraphWidthCheckBox.isSelected())
+        {
+            XYSeries series = new XYSeries("");
+            series.add(trackPool.getLastDetectionTimePoint(), 0);
+            xyDataset.addSeries(series);
+        }
+
+        chartpanel.removeAll();
+
+        if (chart != null)
+        {
+            // replace default chart colors by detection colors (taken from t=0)
+            XYItemRenderer renderer = ((XYPlot) chart.getPlot()).getRenderer();
+            for (TrackSegment ts : trackPool.getTrackSegmentList())
+                renderer.setSeriesPaint(trackPool.getTrackIndex(ts), ts.getFirstDetection().getColor());
+        }
+        chartpanel.add(new ChartPanel(chart, 400, 300, 400, 300, 400, 300, false, false, true, true, true, true));
+
+        chartpanel.updateUI();
+        panel.updateUI();
+
+    }
+
+    /** Export to console and optionaly in xls */
+    private void exportMSD(boolean xlsExport)
+    {
+        WritableWorkbook workbook = null;
+        WritableSheet page = null;
+        String outputName;
+
+        if ((trackPool.getDisplaySequence() == null)
+                || StringUtil.isEmpty(trackPool.getDisplaySequence().getFilename()))
+            outputName = GeneralPreferences.getResultFolder() + "Tracks";
+        else
+            outputName = trackPool.getDisplaySequence().getFilename();
+
+        outputName += ".msd.xls";
+
+        if (xlsExport)
+        {
+            try
+            {
+                workbook = XLSUtil.createWorkbook(new File(outputName));
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+
+        if (workbook != null)
+            page = XLSUtil.createNewPage(workbook, "results");
+
+        final Sequence seq = trackPool.getDisplaySequence();
+        final double tScale = (seq != null) ? seq.getTimeInterval() : 1d;
+
+        if (useRealScalesBox.isSelected())
+        {
+            int cnt = 0;
+            int row = 0;
+            for (TrackSegment ts : trackPool.getTrackSegmentList())
+            {
+                if (ts.isAllDetectionEnabled())
+                {
+                    System.out.println("track " + cnt);
+
+                    if (page != null)
+                        XLSUtil.setCellString(page, 0, row, "track " + cnt);
+
+                    double[] msd = scaledMeanSquaredDisplacement(seq, ts);
+                    for (int i = 0; i < msd.length; i++)
+                    {
+                        System.out.println((i * tScale) + "\t" + msd[i]);
+                        // System.out.println(i + "\t" + msd[i]);
+
+                        if (page != null)
+                            XLSUtil.setCellNumber(page, i + 1, row, msd[i]);
+                    }
+
+                    cnt++;
+                    row++;
+                }
+            }
+        }
+        else
+        {
+            int cnt = 0;
+            int row = 0;
+
+            for (TrackSegment ts : trackPool.getTrackSegmentList())
+            {
+                if (ts.isAllDetectionEnabled())
+                {
+                    System.out.println("track " + cnt);
+
+                    if (page != null)
+                        XLSUtil.setCellString(page, 0, row, "track " + cnt);
+
+                    double[] msd = scaledMeanSquaredDisplacement(null, ts);
+                    // double[] msd = meanSquaredDisplacement(ts);
+
+                    for (int i = 0; i < msd.length; i++)
+                    {
+                        System.out.println(i + "\t" + msd[i]);
+
+                        if (page != null)
+                            XLSUtil.setCellNumber(page, i + 1, row, msd[i]);
+                    }
+
+                    cnt++;
+                    row++;
+                }
+            }
+        }
+
+        if (workbook != null)
+        {
+            try
+            {
+                XLSUtil.saveAndClose(workbook);
+            }
+            catch (WriteException e)
+            {
+                e.printStackTrace();
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    Rectangle2D chartRectangleInSequence = new Rectangle2D.Float(250, 20 + 260 * 0, 490, 240);
+
+    JLabel outLabel = new JLabel();
+
+    private double[] scaledMeanSquaredDisplacement(Sequence seq, TrackSegment ts)
+    {
+        final double sx, sy, sz;
+
+        if (seq != null)
+        {
+            sx = seq.getPixelSizeX();
+            sy = seq.getPixelSizeY();
+            sz = seq.getPixelSizeZ();
+        }
+        else
+        {
+            sx = 1d;
+            sy = 1d;
+            sz = 1d;
+        }
+
+        int nMSD = ts.getDetectionList().size();
+        // double[] dt_n = new double[nMSD];
+        double[] msd = new double[nMSD];
+
+        for (int t = 1; t < nMSD; t++)
+        {
+            msd[t] = 0d;
+            for (int j = 1; j <= t; j++)
+                msd[t] += scaledSquaredDistance(ts.getDetectionAt(0), ts.getDetectionAt(j), sx, sy, sz);
+
+            msd[t] /= t;
+        }
+
+        // for (int dt = 1; dt < nMSD; dt++)
+        // {
+        // for (int j = 0; (j + dt) < nMSD; j += dt)
+        // {
+        // msd[dt] += scaledSquaredDistance(ts.getDetectionAt(j), ts.getDetectionAt(j + dt), sx, sy, sz);
+        // dt_n[dt]++;
+        // }
+        // }
+        //
+        // for (int dt = 1; dt < nMSD; dt++)
+        // msd[dt] = (dt_n[dt] != 0) ? msd[dt] / dt_n[dt] : 0;
+
+        return msd;
+    }
+
+    // mean squared displacement
+    private double[] meanSquaredDisplacement(TrackSegment ts)
+    {
+        int nMSD = ts.getDetectionList().size();
+
+        double[] dt_n = new double[nMSD];
+        double[][] msd = new double[nMSD][2];
+
+        for (int dt = 1; dt < nMSD; dt++)
+        {
+            msd[dt][0] = dt * 1;
+
+            for (int j = 0; j + dt < ts.getDetectionList().size(); j += dt)
+            {
+                msd[dt][1] += squaredDistance(ts.getDetectionAt(j), ts.getDetectionAt(j + dt));
+                dt_n[dt]++;
+            }
+        }
+
+        for (int dt = 1; dt < nMSD; dt++)
+            msd[dt][1] = (dt_n[dt] != 0) ? msd[dt][1] / dt_n[dt] : 0;
+
+        double[] resultmsd = new double[nMSD];
+        resultmsd[0] = 0;
+        for (int dt = 1; dt < nMSD; dt++)
+            resultmsd[dt] = msd[dt][1];
+
+        return resultmsd;
+
+    }
+
+    private static double squaredDistance(Detection d1, Detection d2)
+    {
+        return scaledSquaredDistance(d1, d2, 1d, 1d, 1d);
+    }
+
+    private static double scaledSquaredDistance(Detection d1, Detection d2, double sx, double sy, double sz)
+    {
+        return Math.pow((d1.getX() - d2.getX()) * sx, 2) + Math.pow((d1.getY() - d2.getY()) * sy, 2)
+                + Math.pow((d1.getZ() - d2.getZ()) * sz, 2);
+    }
+
+    // public static double getScaledDistance(double x1, double y1, double z1, double x2, double y2, double z2)
+    // {
+    // return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
+    // }
+    //
+    // public static double getDistance(double x1, double y1, double z1, double x2, double y2, double z2)
+    // {
+    // return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
+    // }
+    //
+    // public double getDistance(Detection d1, Detection d2)
+    // {
+    // return getDistance(d1.getX(), d1.getY(), d1.getZ(), d2.getX(), d2.getY(), d2.getZ());
+    // }
+
+    @Override
+    public void Close()
+    {
+        Sequence sequence = trackPool.getDisplaySequence();
+        if (sequence != null)
+        {
+            sequence.removePainter(this);
+        }
+    }
+
+    public void actionPerformed(ActionEvent e)
+    {
+
+        // if ( e.getSource() == useRoiAsBoundsForChartButton )
+        // {
+        // Shape shape = (Shape) trackPool.getDisplaySequence().getROIs().get( 0 );
+        // chartRectangleInSequence = (Rectangle2D) shape.getBounds2D().clone();
+        // }
+        if (e.getSource() == exportButton)
+        {
+            exportMSD(false);
+        }
+
+        if (e.getSource() == exportExcelButton)
+        {
+            exportMSD(true);
+        }
+
+        trackPool.fireTrackEditorProcessorChange();
+
+    }
+
+    public void keyPressed(Point p, KeyEvent e)
+    {
+
+    }
+
+    public void mouseClick(Point p, MouseEvent e)
+    {
+
+    }
+
+    public void mouseDrag(Point p, MouseEvent e)
+    {
+
+    }
+
+    public void mouseMove(Point p, MouseEvent e)
+    {
+
+    }
+
+    @Override
+    public void displaySequenceChanged()
+    {
+
+    }
+
+    @Override
+    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
+    {
+
+        double scale = Double.parseDouble(scaleTextField.getText());
+        double minX = chartRectangleInSequence.getCenterX();
+        double minY = chartRectangleInSequence.getCenterY();
+
+        Rectangle2D transformedChartRectangleInSequence = (Rectangle2D) chartRectangleInSequence.clone();
+        transformedChartRectangleInSequence.setRect((-chartRectangleInSequence.getWidth() / 2) * (1d / scale),
+                (-chartRectangleInSequence.getHeight() / 2) * (1d / scale),
+                chartRectangleInSequence.getWidth() * (1d / scale),
+                chartRectangleInSequence.getHeight() * (1d / scale));
+
+        Graphics2D g2 = (Graphics2D) g;
+
+        AffineTransform transform = g2.getTransform();
+        g2.scale(scale, scale);
+        g2.translate(minX * (1d / scale), minY * (1d / scale));
+
+        if (displayGraphInSequenceCheckBox.isSelected())
+            chart.draw((Graphics2D) g, transformedChartRectangleInSequence);
+
+        g2.setTransform(transform);
+
+        // if ( displayGraphInSequenceCheckBox.isSelected() )
+        // chart.draw( (Graphics2D)g , chartRectangleInSequence );
+
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void mouseClick(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+    @Override
+    public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
+    {
+
+    }
+
+}