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..a0e91f16fba6dbe004302456ac236144c7d2b216
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,89 @@
+<?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>protractor</artifactId>
+    <version>1.1.0</version>
+
+    <packaging>jar</packaging>
+
+    <name>Protractor</name>
+    <description>A protractor to measure angles on images </description>
+    <url>http://icy.bioimageanalysis.org/plugin/protractor/</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>ruler-helper</artifactId>
+            <version>1.2.1</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/protractor/Protractor.class b/src/main/java/plugins/fab/protractor/Protractor.class
new file mode 100644
index 0000000000000000000000000000000000000000..4ea6bec10a84e84a8c6e6ce2dd3da5fa604a7183
Binary files /dev/null and b/src/main/java/plugins/fab/protractor/Protractor.class differ
diff --git a/src/main/java/plugins/fab/protractor/Protractor.java b/src/main/java/plugins/fab/protractor/Protractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..d746afd7a6622808064df0848cfd71a25c375cd6
--- /dev/null
+++ b/src/main/java/plugins/fab/protractor/Protractor.java
@@ -0,0 +1,30 @@
+/**
+ * by Fabrice de Chaumont
+ */
+
+package plugins.fab.protractor;
+
+import icy.gui.dialog.MessageDialog;
+import icy.plugin.abstract_.Plugin;
+import icy.plugin.interface_.PluginImageAnalysis;
+import icy.sequence.Sequence;
+
+public class Protractor extends Plugin implements PluginImageAnalysis {
+
+	@Override
+	public void compute() {
+
+
+		Sequence sequence = getFocusedSequence();
+		
+		if ( sequence!=null )
+		{
+			new ProtractorPainter( getFocusedSequence() );
+		}else
+		{
+			MessageDialog.showDialog("Please open an image first.", MessageDialog.INFORMATION_MESSAGE );
+		}
+		
+	}
+
+}
diff --git a/src/main/java/plugins/fab/protractor/ProtractorPainter.class b/src/main/java/plugins/fab/protractor/ProtractorPainter.class
new file mode 100644
index 0000000000000000000000000000000000000000..375e3f6504d4a3fbf9f104f757d81707c770e23c
Binary files /dev/null and b/src/main/java/plugins/fab/protractor/ProtractorPainter.class differ
diff --git a/src/main/java/plugins/fab/protractor/ProtractorPainter.java b/src/main/java/plugins/fab/protractor/ProtractorPainter.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a5ace94cbce92e2d09166de8fc6247838a955fd
--- /dev/null
+++ b/src/main/java/plugins/fab/protractor/ProtractorPainter.java
@@ -0,0 +1,415 @@
+/**
+ * by Fabrice de Chaumont
+ */
+package plugins.fab.protractor;
+
+import icy.canvas.IcyCanvas;
+import icy.main.Icy;
+import icy.math.Scaler;
+import icy.painter.Anchor2D;
+import icy.painter.Anchor2D.Anchor2DListener;
+import icy.painter.Overlay;
+import icy.painter.PainterEvent;
+import icy.sequence.Sequence;
+import icy.type.point.Point5D;
+import icy.type.point.Point5D.Double;
+import icy.util.GraphicsUtil;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+
+import plugins.fab.Ruler.Anchor2DTarget;
+import plugins.fab.Ruler.ShapeDefinition;
+
+public class ProtractorPainter extends Overlay implements Anchor2DListener {
+
+	Anchor2DTarget a1 = new Anchor2DTarget( 0 , 0 );
+	Anchor2DTarget a2 = new Anchor2DTarget( 0 , 0 );
+	ArrayList<ShapeDefinition> shapeDefinitionList = new ArrayList<ShapeDefinition>();
+	ArrayList<AffineTransform> affineTransformList = new ArrayList<AffineTransform>();
+	
+	
+	public ProtractorPainter(Sequence sequence) {
+
+		super("Protractor");
+		if ( sequence == null ) return;
+
+		sequence.addOverlay( this );		
+		
+		a1.setPosition( sequence.getWidth() / 2 , sequence.getHeight() / 2 );
+		a2.setPosition( 3 * sequence.getWidth() / 4 , sequence.getHeight() / 2 );
+
+//		sequence.addPainter( this );
+		a1.addAnchorListener( this );
+		a2.addAnchorListener( this );
+		
+	}
+	
+	// Listener of Anchor
+	
+	@Override
+	public void painterChanged(PainterEvent event) {
+		
+		for ( Sequence sequence : Icy.getMainInterface().getSequencesContaining( this ) )
+		{
+			sequence.overlayChanged( this );
+//			sequence.painterChanged( this );
+		}
+		
+	}
+
+	@Override
+	public void positionChanged(Anchor2D source) {
+		
+		
+	}
+	
+
+    int findBestMajTickSpace(int sliderSize, int delta)
+    {
+    	
+        final int values[] = { 5, 10, 20, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000};
+        
+        
+        int wantedMajTickSpace;
+        // wanted a major tick each ~40 pixels
+        try
+        {
+        	wantedMajTickSpace = delta / (sliderSize / 40);
+        }
+        catch( ArithmeticException e )
+        {
+        	return values[0];
+        }
+
+        int min = Integer.MAX_VALUE;
+        int bestValue = 5;
+
+        // try with our predefined values
+        for (int value : values)
+        {
+            final int dx = Math.abs(value - wantedMajTickSpace);
+
+            if (dx < min)
+            {
+                min = dx;
+                bestValue = value;
+            }
+        }
+
+        return bestValue;
+    }
+	
+	void pushTransform( Graphics2D g )
+	{
+		affineTransformList.add( g.getTransform() );
+	}
+
+	void popTransform( Graphics2D g )
+	{
+		g.setTransform( affineTransformList.get( affineTransformList.size() -1 ) );
+		affineTransformList.remove( affineTransformList.size() -1 );
+	}
+	
+	
+	public Point2D getPointLocation( double angle, double distance2 )
+	{
+		angle = -angle * ( Math.PI / 180.0 );
+		//distance2 = 100;
+		
+		Line2D line = new Line2D.Double( a1.getPosition() , a2.getPosition() );
+		
+		// perform rotation.
+	
+		AffineTransform transform = new AffineTransform();
+		
+		double distance = line.getP1().distance( line.getP2() );
+		double vx = ( line.getP2().getX() - line.getP1().getX() ) / distance;
+		double vy = ( line.getP2().getY() - line.getP1().getY() ) / distance;
+				
+		//transform.translate( line.getX1() , line.getY1() );
+		transform.rotate( -Math.atan2( vy , vx ) , 0 , 0 );		
+
+		Point2D searchedPoint = new Point2D.Double( Math.cos( angle ) * distance2 , Math.sin( angle ) * distance2 );
+		Point2D p = new Point2D.Double( 0,0 );
+		
+		try {
+			transform.inverseTransform( searchedPoint , p );
+		} catch (NoninvertibleTransformException e) {
+			e.printStackTrace();
+		}
+		
+		p.setLocation( p.getX() + a1.getPosition().getX() , p.getY() + a1.getPosition().getY() );
+				
+		return p;
+	}
+	
+	// Painter Section:
+
+	@Override
+	public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) {
+		
+		g= (Graphics2D) g.create();
+		
+		BasicStroke[] stroke = new BasicStroke[4];
+		
+		
+		stroke[0] = new BasicStroke((float) canvas.canvasToImageLogDeltaX( 2 ) ); 
+		stroke[1] = new BasicStroke((float) canvas.canvasToImageLogDeltaX( 3 ) ); 
+		stroke[2] = new BasicStroke((float) canvas.canvasToImageLogDeltaX( 4 ) ); 
+		stroke[3] = new BasicStroke((float) canvas.canvasToImageLogDeltaX( 5 ) ); 
+		
+		Line2D line = new Line2D.Double( a1.getPosition() , a2.getPosition() );
+		
+		// transform and display ticks
+
+		shapeDefinitionList.clear();
+	
+		pushTransform( g );
+		
+		double distance = line.getP1().distance( line.getP2() );
+		double vx = ( line.getP2().getX() - line.getP1().getX() ) / distance;
+		double vy = ( line.getP2().getY() - line.getP1().getY() ) / distance;
+		
+		g.translate( line.getX1() , line.getY1() );
+		g.rotate( Math.atan2( vy , vx ) , 0 , 0 );		
+		
+		double minorTickDistance = 9d * distance / 10d;
+		double subMinorTickDistance = 95d * distance / 100d;
+		double firstDistance = distance / 10d;
+		double secondDistance = 3* distance / 10d;
+		
+		// each 90° lines
+
+		for ( int i=0 ; i< 360 ; i+=90 )
+		{
+			double angle = i * ( Math.PI / 180.0 );
+			
+			shapeDefinitionList.add( new ShapeDefinition( 1 , 
+					new Line2D.Double( Math.cos( angle ) * firstDistance , Math.sin( angle ) * firstDistance ,
+							Math.cos( angle ) * ( 3d * distance / 2d ) , Math.sin( angle ) * ( 3d * distance / 2d ) )
+			) );
+		}
+
+		// each 45° lines
+
+		for ( int i=45 ; i< 360 ; i+=90 )
+		{
+			double angle = i * ( Math.PI / 180.0 );
+			
+			shapeDefinitionList.add( new ShapeDefinition( 2 , 
+					new Line2D.Double( Math.cos( angle ) * secondDistance , Math.sin( angle ) * secondDistance ,
+							Math.cos( angle ) * distance , Math.sin( angle ) * distance )
+			) );
+		}
+
+		// circles
+		
+		shapeDefinitionList.add(  new ShapeDefinition( 1 ,
+				new Ellipse2D.Double( -distance , -distance , distance*2 , distance*2 )
+				) );
+
+		shapeDefinitionList.add(  new ShapeDefinition( 1 ,
+				new Ellipse2D.Double( -firstDistance , -firstDistance , firstDistance*2 , firstDistance*2 )
+				) );
+		
+		// ticks each 5 degrees
+		
+		for ( int i=0 ; i< 360 ; i+=5 )
+		{
+			double angle = i * ( Math.PI / 180.0 );
+			
+			shapeDefinitionList.add( new ShapeDefinition( 1 , 
+					new Line2D.Double( Math.cos( angle ) * minorTickDistance , Math.sin( angle ) * minorTickDistance ,
+							Math.cos( angle ) * distance , Math.sin( angle ) * distance )
+			) );
+		}
+
+		double distanceBetweenTicks;
+		{
+			double a = 0;
+			Point2D p1 = new Point2D.Double( Math.cos( a ) * distance , Math.sin( a ) * distance );
+			a+=Math.PI / 18d;
+			Point2D p2 = new Point2D.Double( Math.cos( a ) * distance , Math.sin( a ) * distance );
+			distanceBetweenTicks = canvas.getScaleX() * p1.distance( p2 );
+		}
+		
+		// ticks each 1 degree
+		
+		Scaler scalerSubMinorTickAlpha = new Scaler( 40 , 50 , 0d , 1d , false );
+		float alphaSubMinor = (float) scalerSubMinorTickAlpha.scale( distanceBetweenTicks );
+		
+		if ( distanceBetweenTicks > 40 )
+		{
+			for ( int i=0 ; i< 360 ; i++ )
+			{
+				if ( i%5 == 0 ) continue;
+				
+				double angle = i * ( Math.PI / 180.0 );
+
+				shapeDefinitionList.add( new ShapeDefinition( 1 , 
+						new Line2D.Double( Math.cos( angle ) * subMinorTickDistance , Math.sin( angle ) * subMinorTickDistance ,
+								Math.cos( angle ) * distance , Math.sin( angle ) * distance ) , alphaSubMinor
+						) );
+			}
+		}
+		
+		
+		// draw lines ( black background, then white )
+		
+		float oldAlpha= -1;
+		g.setColor( Color.black );
+		for ( ShapeDefinition ld : shapeDefinitionList )
+		{
+			if (oldAlpha!=ld.alpha)
+			{
+				g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ld.alpha ) );
+				oldAlpha = ld.alpha;
+			}
+			g.setStroke( stroke[ld.stroke] );
+			g.draw( ld.shape );
+		}
+
+		g.setColor( Color.white );
+		for ( ShapeDefinition ld : shapeDefinitionList )
+		{
+			if (oldAlpha!=ld.alpha)
+			{
+				g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ld.alpha ) );
+				oldAlpha = ld.alpha;
+			}
+			g.setStroke( stroke[ld.stroke-1] );
+			g.draw( ld.shape );
+		}
+		
+		// draw degrees text
+
+		Scaler scalerMajorTickAlpha = new Scaler( 12 , 20 , 0d , 1d , false );
+		float alphaMajor = (float) scalerMajorTickAlpha.scale( distanceBetweenTicks );
+		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alphaMajor ) );
+
+		if ( distanceBetweenTicks > 12 )
+		{
+			int fontSize =  (int)convertScale(canvas, 15 );
+			Font font =  new Font( "Arial" , Font.PLAIN , fontSize ); 
+			for ( int i = 0 ; i < 360 ; i+=45 )
+			{
+				pushTransform( g );
+				String pixelString = " "+ (int)i + "° ";
+				Rectangle2D pixelBounds = GraphicsUtil.getStringBounds( g , font, pixelString );
+				g.rotate( -i * Math.PI / 180d );
+				g.translate( distance/2 - pixelBounds.getWidth()/2 , pixelBounds.getHeight()/4 ) ; //-convertScale( canvas, 20 ) );
+				g.setFont( font );
+				g.setColor( Color.white );
+				g.fill( pixelBounds );
+				g.setColor( Color.black );
+				g.drawString( pixelString , 0, 0 );
+				popTransform(g);
+			}
+		}
+		
+		Scaler scalerMinorTickAlpha = new Scaler( 18 , 30 , 0d , 1d , false );
+		float alphaMinor = (float) scalerMinorTickAlpha.scale( distanceBetweenTicks );
+		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alphaMinor ) );
+		
+		if ( distanceBetweenTicks > 18 )
+		{
+			int fontSize =  (int)convertScale(canvas, 10 );
+			Font font =  new Font( "Arial" , Font.PLAIN , fontSize ); 
+			for ( int i = 0 ; i < 360 ; i+=10 )
+			{
+				pushTransform( g );
+				String pixelString = " "+ (int)i + "° ";
+				Rectangle2D pixelBounds = GraphicsUtil.getStringBounds( g , font, pixelString );
+				g.rotate( -i * Math.PI / 180d );
+				g.translate( 5d*distance/6d - pixelBounds.getWidth()/2 , pixelBounds.getHeight()/4 ) ; //-convertScale( canvas, 20 ) );
+				g.setFont( font );
+				g.setColor( Color.white );
+				g.fill( pixelBounds );
+				g.setColor( Color.black );
+				g.drawString( pixelString , 0, 0 );
+				popTransform( g );
+			}
+		}				
+		
+		// get back to original transform
+				
+		popTransform( g );
+
+		// display anchors
+		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1 ) );
+		a1.paint( g, sequence, canvas);
+		a2.paint( g, sequence, canvas);
+	}
+		
+	double convertScale( IcyCanvas canvas , double value )
+	{
+		return canvas.canvasToImageLogDeltaX( (int) value ); 
+	}
+
+	
+	@Override
+	public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.mousePressed(e, imagePoint, canvas);		
+		a2.mousePressed(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.mouseReleased(e, imagePoint, canvas);
+		a2.mouseReleased(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.mouseClick(e, imagePoint, canvas);
+		a2.mouseClick(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.mouseMove(e, imagePoint, canvas);
+		a2.mouseMove(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.mouseDrag(e, imagePoint, canvas);
+		a2.mouseDrag(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+				
+		if ( ( e.getKeyCode() == KeyEvent.VK_DELETE && a1.isSelected() ) || ( e.getKeyCode() == KeyEvent.VK_DELETE && a2.isSelected() ) )
+		{			
+			for ( Sequence sequence : Icy.getMainInterface().getSequencesContaining( this ) )
+			{
+				sequence.removePainter( this );
+			}
+			
+		}
+			
+		a1.keyPressed(e, imagePoint, canvas);
+		a2.keyPressed(e, imagePoint, canvas);
+	}
+
+	@Override
+	public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) {
+		a1.keyReleased(e, imagePoint, canvas);
+		a2.keyReleased(e, imagePoint, canvas);
+	}	
+	
+}