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/README.txt b/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..82c301b2587fe1f62cd755dc18011241ed0544e3 --- /dev/null +++ b/README.txt @@ -0,0 +1,8 @@ +Icy plugin to display ToolTip on the current focused ROI. + +The tooltip displays various informations : +- ROI position & size +- min, max, mean intensity. +- number of pixels. + +This plugin is a daemon plugin, that means plugin is automatically loaded when ICY starts. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..1f12d2d6e7f7957625c0aea07e2ed3ee4d53333b --- /dev/null +++ b/pom.xml @@ -0,0 +1,87 @@ +<?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>roi-tooltip</artifactId> + <version>1.1.4</version> + + <packaging>jar</packaging> + + <name>ROI Tooltip</name> + <description> + Displays a live tool tip on the current focused ROI in an image. The tooltip displays the following informations about the ROI: - + position and size. - number of interior points and contour points. - perimeter, area, surface area, volume. - min, max, mean + intensity. This plugin is a daemon plugin, that means plugin is automatically loaded when Icy starts. + </description> + <url>http://icy.bioimageanalysis.org/plugin/roi-tooltip/</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> + </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> \ No newline at end of file diff --git a/src/main/java/plugins/stef/roi/ROIToolTip.java b/src/main/java/plugins/stef/roi/ROIToolTip.java new file mode 100644 index 0000000000000000000000000000000000000000..808c80c06974ce0555ad7cc50306b667f7be7a15 --- /dev/null +++ b/src/main/java/plugins/stef/roi/ROIToolTip.java @@ -0,0 +1,357 @@ +package plugins.stef.roi; + +import icy.canvas.IcyCanvas; +import icy.canvas.IcyCanvas2D; +import icy.canvas.Layer; +import icy.gui.main.ActiveViewerListener; +import icy.gui.viewer.Viewer; +import icy.gui.viewer.ViewerEvent; +import icy.image.IntensityInfo; +import icy.main.Icy; +import icy.math.MathUtil; +import icy.painter.Overlay; +import icy.plugin.abstract_.Plugin; +import icy.plugin.interface_.PluginDaemon; +import icy.roi.ROI; +import icy.roi.ROI2D; +import icy.roi.ROI3D; +import icy.roi.ROIEvent; +import icy.roi.ROIEvent.ROIEventType; +import icy.roi.ROIListener; +import icy.roi.ROIUtil; +import icy.sequence.Sequence; +import icy.system.thread.SingleProcessor; +import icy.util.GraphicsUtil; +import icy.util.StringUtil; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; + +/** + * ROI Tooltip plugin.<br> + * Display a tool tip with extras informations on focused ROI in Canvas2D. + * + * @author Stephane + */ +public class ROIToolTip extends Plugin implements PluginDaemon, ActiveViewerListener, ROIListener +{ + private class ROICalculator implements Runnable + { + public ROICalculator() + { + super(); + } + + @Override + public void run() + { + final Sequence seq = activeSequence; + final ROI roi = focusedROI; + + try + { + if ((seq != null) && (roi != null)) + { + intensityInfo = ROIUtil.getIntensityInfo(activeSequence, focusedROI); + points = MathUtil.roundSignificant(roi.getNumberOfPoints(), 2, true); + contourPoints = MathUtil.roundSignificant(roi.getNumberOfContourPoints(), 2, true); + perimeter = ROIUtil.getPerimeter(activeSequence, roi); + area = ROIUtil.getArea(activeSequence, roi); + surfaceArea = ROIUtil.getSurfaceArea(activeSequence, roi); + volume = ROIUtil.getVolume(activeSequence, roi); + } + } + catch (Exception e) + { + // async process, ROI can change in the meantime + } + + overlay.painterChanged(); + } + } + + private class HintOverlay extends Overlay + { + public HintOverlay() + { + super("ROI tip", OverlayPriority.TOOLTIP_LOW); + } + + private String enlarge(String text, int len) + { + String result = text; + while (result.length() < len) + result += ' '; + return result; + } + + @Override + public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) + { + if (canvas instanceof IcyCanvas2D) + { + final IcyCanvas2D cnv2d = (IcyCanvas2D) canvas; + ROI roi = sequence.getFocusedROI(); + + if (roi == null) + { + // search in selected ROI + final ROI2D selectedRoi = sequence.getSelectedROI2D(); + + // assume this is a focused ROI + if ((selectedRoi != null) && (selectedRoi.hasSelectedPoint())) + roi = selectedRoi; + } + + if (roi != null) + { + roiFocused(roi); + + if (roi instanceof ROI2D) + { + final Rectangle2D bounds = ((ROI2D) roi).getBounds2D(); + final List<String> text = new ArrayList<String>(); + final String perim = perimeter; + final String surfArea = surfaceArea; + + // if (processor.isProcessing()) + // updatingMark = "*"; + // else + // updatingMark = ""; + + // draw roi informations in the tooltip + text.add("Position X " + StringUtil.toString(bounds.getX(), 1)); + text.add("Size X " + StringUtil.toString(bounds.getWidth(), 1)); + text.add("Interior " + StringUtil.toString(points) + " px"); + if (!StringUtil.isEmpty(perim)) + text.add("Perimeter " + perim); + if (!StringUtil.isEmpty(surfArea)) + text.add("Surf. area " + surfArea); + + int maxLength = 0; + for (String t : text) + maxLength = Math.max(maxLength, t.length()); + maxLength += 2; + + String tooltipText = ""; + int ind = 0; + + tooltipText = enlarge(text.get(ind++), maxLength); + tooltipText += "Position Y " + StringUtil.toString(bounds.getY(), 1) + "\n"; + tooltipText += enlarge(text.get(ind++), maxLength); + tooltipText += "Size Y " + StringUtil.toString(bounds.getHeight(), 1) + "\n"; + tooltipText += enlarge(text.get(ind++), maxLength); + tooltipText += "Contour " + StringUtil.toString(contourPoints) + " px" + "\n"; + + if (!StringUtil.isEmpty(perim)) + { + tooltipText += enlarge(text.get(ind++), maxLength); + tooltipText += "Area " + area + "\n"; + } + if (!StringUtil.isEmpty(surfArea)) + { + tooltipText += enlarge(text.get(ind++), maxLength); + tooltipText += "Volume " + volume + "\n"; + } + + if (intensityInfo != null) + { + tooltipText += "Intensity "; + tooltipText += "min: " + StringUtil.toString(intensityInfo.minIntensity, 1) + " "; + tooltipText += "max: " + StringUtil.toString(intensityInfo.maxIntensity, 1) + " "; + tooltipText += "mean: " + StringUtil.toString(intensityInfo.meanIntensity, 1); + } + + final Graphics2D g2 = (Graphics2D) g.create(); + + g2.transform(cnv2d.getInverseTransform()); + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g2.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + + // default position + Point pos = cnv2d.getMousePosition(true); + if (pos == null) + pos = new Point((int) bounds.getMinX(), (int) bounds.getMaxY()); + + // canvas visible region + final Rectangle region = cnv2d.getCanvasVisibleRect(); + // hint size + final Dimension hintSize = GraphicsUtil.getHintSize(g2, tooltipText); + + // get best hint position to be visible + pos = GraphicsUtil.getBestPosition(pos, hintSize, region, 8, 8); + + GraphicsUtil.drawHint(g2, tooltipText, pos.x, pos.y, Color.gray, Color.white); + + g2.dispose(); + } + else if (roi instanceof ROI3D) + { + // not yet supported + + } + } + } + } + } + + Viewer activeViewer; + Sequence activeSequence; + ROI focusedROI; + + final HintOverlay overlay; + final SingleProcessor processor; + + /** + * ROI calculated infos + */ + IntensityInfo intensityInfo; + double points; + double contourPoints; + String perimeter; + String area; + String surfaceArea; + String volume; + + public ROIToolTip() + { + super(); + + overlay = new HintOverlay(); + intensityInfo = new IntensityInfo(); + processor = new SingleProcessor(true); + } + + void updateInfos() + { + processor.submit(new ROICalculator()); + } + + @Override + public void init() + { + activeViewer = null; + activeSequence = null; + focusedROI = null; + + viewerActivated(getActiveViewer()); + roiFocused((activeSequence != null) ? activeSequence.getFocusedROI() : null); + + Icy.getMainInterface().addActiveViewerListener(this); + } + + @Override + public void run() + { + // nothing to do here + } + + @Override + public void stop() + { + Icy.getMainInterface().removeActiveViewerListener(this); + + viewerActivated(null); + roiFocused(null); + } + + public void sequenceActivated(Sequence sequence) + { + if (activeSequence != sequence) + { + if (activeSequence != null) + activeSequence.removeOverlay(overlay); + + activeSequence = sequence; + roiFocused(null); + + if (activeSequence != null) + activeSequence.addOverlay(overlay); + } + } + + public void roiFocused(ROI roi) + { + if (focusedROI != roi) + { + if (focusedROI != null) + focusedROI.removeListener(this); + + focusedROI = roi; + + if (focusedROI != null) + focusedROI.addListener(this); + + updateInfos(); + } + } + + @Override + public void roiChanged(ROIEvent event) + { + if (event.getType() == ROIEventType.ROI_CHANGED) + updateInfos(); + } + + @Override + public void viewerActivated(Viewer viewer) + { + if (activeViewer != viewer) + { + float alpha = 1f; + + if (activeViewer != null) + { + final IcyCanvas canvas = activeViewer.getCanvas(); + if (canvas != null) + { + final Layer layer = canvas.getLayer(overlay); + if (layer != null) + alpha = layer.getAlpha(); + } + } + + activeViewer = viewer; + + final Sequence sequence; + if (activeViewer != null) + sequence = activeViewer.getSequence(); + else + sequence = null; + + sequenceActivated(sequence); + + if (activeViewer != null) + { + final IcyCanvas canvas = activeViewer.getCanvas(); + if (canvas != null) + { + final Layer layer = canvas.getLayer(overlay); + if (layer != null) + layer.setAlpha(alpha); + } + } + } + } + + @Override + public void viewerDeactivated(Viewer viewer) + { + // nothing here + } + + @Override + public void activeViewerChanged(ViewerEvent event) + { + // nothing here + } +} \ No newline at end of file diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9c72a7fce0468de8669dc1ac9762a5d363472df5 Binary files /dev/null and b/src/main/resources/icon.png differ diff --git a/src/main/resources/roitooltip.png b/src/main/resources/roitooltip.png new file mode 100644 index 0000000000000000000000000000000000000000..d4144f4795b0bf93220397d7935726178b32149a Binary files /dev/null and b/src/main/resources/roitooltip.png differ