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..9ab8c4ab02e1ed9a813eb595e08c6754924ebb8e --- /dev/null +++ b/pom.xml @@ -0,0 +1,95 @@ +<?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>angle-helper</artifactId> + <version>1.1.0</version> + + <packaging>jar</packaging> + + <name>Angle Helper</name> + <description>This repo serves as a template for you to implement new Icy plugins.</description> + <url>http://icy.bioimageanalysis.org/plugin/angle-helper/</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>xuggler</artifactId> + <version>5.5.2</version> + </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/anglehelper/AngleHelper.java b/src/main/java/plugins/fab/anglehelper/AngleHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..58f19050f4d66183e261b9b8aff033b5a71653db --- /dev/null +++ b/src/main/java/plugins/fab/anglehelper/AngleHelper.java @@ -0,0 +1,28 @@ +package plugins.fab.anglehelper; + +import icy.gui.dialog.MessageDialog; +import icy.plugin.abstract_.Plugin; +import icy.plugin.interface_.PluginImageAnalysis; +import icy.sequence.Sequence; + +/** + * @author Fabrice de Chaumont + */ +public class AngleHelper extends Plugin implements PluginImageAnalysis { + + @Override + public void compute() { + + Sequence sequence = getFocusedSequence(); + + if ( sequence!=null ) + { + new AnglePainter( getFocusedSequence() ); + }else + { + MessageDialog.showDialog("Please open an image first.", MessageDialog.INFORMATION_MESSAGE ); + } + + } + +} diff --git a/src/main/java/plugins/fab/anglehelper/AnglePainter.java b/src/main/java/plugins/fab/anglehelper/AnglePainter.java new file mode 100644 index 0000000000000000000000000000000000000000..b6714ed2ae3a5979369700e5d96d76fddf8aac36 --- /dev/null +++ b/src/main/java/plugins/fab/anglehelper/AnglePainter.java @@ -0,0 +1,336 @@ +package plugins.fab.anglehelper; + +import icy.canvas.IcyCanvas; +import icy.canvas.IcyCanvas2D; +import icy.gui.util.GuiUtil; +import icy.main.Icy; +import icy.math.Scaler; +import icy.painter.Anchor2D; +import icy.painter.Anchor2D.Anchor2DListener; +import icy.painter.Painter; +import icy.painter.PainterEvent; +import icy.roi.ROI2D; +import icy.sequence.Sequence; + +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.Arc2D; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; + +import plugins.fab.Ruler.Anchor2DTarget; +import plugins.fab.Ruler.ShapeDefinition; + +/** + * @author Fabrice de Chaumont + */ +public class AnglePainter implements Painter, Anchor2DListener { + + Anchor2DTarget a1 = new Anchor2DTarget( 0 , 0 ); + Anchor2DTarget a2 = new Anchor2DTarget( 0 , 0 ); + Anchor2DTarget a3 = new Anchor2DTarget( 0 , 0 ); + + ArrayList<ShapeDefinition> shapeDefinitionList = new ArrayList<ShapeDefinition>(); + ArrayList<AffineTransform> affineTransformList = new ArrayList<AffineTransform>(); + + public AnglePainter(Sequence sequence) { + + if ( sequence == null ) return; + + a1.setPosition( sequence.getWidth() / 2 , sequence.getHeight() / 2 ); + a2.setPosition( 3 * sequence.getWidth() / 4 , sequence.getHeight() / 2 ); + a3.setPosition( 3 * sequence.getWidth() / 4 , sequence.getHeight() / 3 ); + + sequence.addPainter( this ); + a1.addListener( this ); + a2.addListener( this ); + a3.addListener( this ); + + } + + // Listener of Anchor + + @Override + public void painterChanged(PainterEvent event) { + + for ( Sequence sequence : Icy.getMainInterface().getSequencesContaining( this ) ) + { + sequence.painterChanged( this ); + } + + } + + @Override + public void positionChanged(Anchor2D source) { + + + } + + 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 double CleanAngle( double angle ) + { + double a = Math.abs( angle ); + a = Math.round(a*10)/10.; + return a; + } + + // Painter Section: + + @Override + public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { + + if ( ! ( canvas instanceof IcyCanvas2D ) ) return; + + g= (Graphics2D) g.create(); + + BasicStroke[] stroke = new BasicStroke[4]; + + stroke[0] = new BasicStroke((float) ROI2D.canvasToImageLogDeltaX(canvas, 2 ) ); + stroke[1] = new BasicStroke((float) ROI2D.canvasToImageLogDeltaX(canvas, 3 ) ); + stroke[2] = new BasicStroke((float) ROI2D.canvasToImageLogDeltaX(canvas, 4 ) ); + stroke[3] = new BasicStroke((float) ROI2D.canvasToImageLogDeltaX(canvas, 5 ) ); + + // transform and display ticks + + shapeDefinitionList.clear(); + + pushTransform( g ); + + double vx12 = ( a2.getX() - a1.getX() ) ; + double vy12 = ( a2.getY() - a1.getY() ) ; + double angle12 = Math.atan2( vy12 , vx12 ) * ( 180d / Math.PI ) ; + + double vx13 = ( a3.getX() - a1.getX() ) ; + double vy13 = ( a3.getY() - a1.getY() ) ; + double angle13 = Math.atan2( vy13 , vx13 ) * ( 180d / Math.PI ); + + double angle1 = angle12-angle13; + + if ( angle1 < 0 ) angle1+=360; + + double angle2 = 360-angle1; + + double dis1 = a1.getPosition().distance( a2.getPosition() ); + double dis2 = a1.getPosition().distance( a3.getPosition() ); + + double minDistance = Math.min( dis1 , dis2 ); + + double distanceAngle1 = minDistance ; + double distanceAngle2 = minDistance / 2; + + double distanceTextAngle1 = minDistance; + double distanceTextAngle2 = minDistance/2; + + // Lines + + shapeDefinitionList.add( new ShapeDefinition( 2 , new Line2D.Double( a1.getPosition() , a2.getPosition() ) ) ); + shapeDefinitionList.add( new ShapeDefinition( 2 , new Line2D.Double( a1.getPosition() , a3.getPosition() ) ) ); + + // angle 1 + { + double arcSize = distanceAngle1; + Arc2D arc = new Arc2D.Double( + a1.getX() - arcSize/2d, + a1.getY() - arcSize/2d, + arcSize, + arcSize, + -angle12 , + angle1,//angle13, + Arc2D.OPEN + ); + + shapeDefinitionList.add( new ShapeDefinition( 2 , arc ) ); + } + + // angle 2 + { + double arcSize = distanceAngle2; + Arc2D arc = new Arc2D.Double( + a1.getX() - arcSize/2d, + a1.getY() - arcSize/2d, + arcSize, + arcSize, + -angle13 , + angle2,//angle13, + Arc2D.OPEN + ); + + shapeDefinitionList.add( new ShapeDefinition( 2 , arc ) ); + } + + // 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 ); + } + + // Display angle text + + { + int fontSize = (int)convertScale(canvas, 15 ); + Font font = new Font( "Arial" , Font.PLAIN , fontSize ); + + pushTransform( g ); + String pixelString = " "+ (int)( Math.round( angle1 ) ) + "° "; + Rectangle2D pixelBounds = GuiUtil.getStringBounds( g , font, pixelString ); + g.translate( a1.getX() , a1.getY() ); + + double angleText = Math.toRadians( angle12 - angle1 / 2d ); + g.translate( Math.cos( angleText ) * distanceTextAngle1 /2 , Math.sin( angleText ) * distanceTextAngle1 /2 ); + g.translate( -pixelBounds.getWidth() / 2 , 0 ); + + g.setFont( font ); + g.setColor( Color.white ); + g.fill( pixelBounds ); + g.setColor( Color.black ); + g.drawString( pixelString , 0, 0 ); + popTransform( g ); + } + + { + int fontSize = (int)convertScale(canvas, 15 ); + Font font = new Font( "Arial" , Font.PLAIN , fontSize ); + + pushTransform( g ); + String pixelString = " "+ (int)(Math.round( angle2 ) ) + "� "; + Rectangle2D pixelBounds = GuiUtil.getStringBounds( g , font, pixelString ); + g.translate( a1.getX() , a1.getY() ); + + double angleText = Math.toRadians( 180 + angle12 - angle1 / 2d ); + g.translate( Math.cos( angleText ) * distanceTextAngle2 / 2d , Math.sin( angleText ) * distanceTextAngle2 / 2d ); + g.translate( -pixelBounds.getWidth() / 2 , 0 ); + + float alpha = 1 ; + double distance = canvas.getScaleX() * distanceTextAngle2 / 2d ; + + Scaler scaler = new Scaler( 0 , 10 , 0, 1 , false ); + alpha = (float)scaler.scale( distance - pixelBounds.getWidth() / 10d ); + + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha ) ); + + g.setFont( font ); + g.setColor( Color.white ); + g.fill( pixelBounds ); + g.setColor( Color.black ); + g.drawString( pixelString , 0, 0 ); + + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1 ) ); + 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); + a3.paint( g, sequence, canvas); + } + + double convertScale( IcyCanvas canvas , double value ) + { + return ROI2D.canvasToImageLogDeltaX(canvas, value ); + } + + @Override + public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.mousePressed(e, imagePoint, canvas); + a2.mousePressed(e, imagePoint, canvas); + a3.mousePressed(e, imagePoint, canvas); + } + + @Override + public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.mouseReleased(e, imagePoint, canvas); + a2.mouseReleased(e, imagePoint, canvas); + a3.mouseReleased(e, imagePoint, canvas); + } + + @Override + public void mouseClick(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.mouseClick(e, imagePoint, canvas); + a2.mouseClick(e, imagePoint, canvas); + a3.mouseClick(e, imagePoint, canvas); + + } + + @Override + public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.mouseMove(e, imagePoint, canvas); + a2.mouseMove(e, imagePoint, canvas); + a3.mouseMove(e, imagePoint, canvas); + } + + @Override + public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.mouseDrag(e, imagePoint, canvas); + a2.mouseDrag(e, imagePoint, canvas); + a3.mouseDrag(e, imagePoint, canvas); + } + + @Override + public void keyPressed(KeyEvent e, Point2D 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); + a3.keyPressed(e, imagePoint, canvas); + } + + @Override + public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) { + a1.keyReleased(e, imagePoint, canvas); + a2.keyReleased(e, imagePoint, canvas); + a3.keyReleased(e, imagePoint, canvas); + } + +}