Where is the ROI Picker?

I would need a ROI picker to select individual ROIs in ROI manager by clicking inside a ROI in an image that was previously segmented and Analyze particles was ran.

I found a post in the ImageJ mailing list asking for how to use the ROI picker that is a part of the Grid / Collection stitching plugin: http://imagej.1557.x6.nabble.com/where-is-the-ROI-Picker-td5018252.html
(I intentionally reused the same title for this topic)

There is even a documentation page about it in the ImageJ website: https://imagej.net/ROI_Picker, pointing to the source on GitHub, where the Stitching plugins developed by Stephan Preibisch are published.

However, as the brief discussion in the ImageJ forum indicates, the plugin doesn’t work alone. When I tried to install it as a plugin it compiles with some errors (below) and the tool icon appears in the toolbar, but the tool doesn’t do anything.

The errors in compiling RoiPicker.java are java related, as I probably don’t have the Java environment properly setup:

RoiPicker not up-to-date because 1 source files are not up-to-date (C:\Users\...\AppData\Local\Temp\java4541506770797815540\src\main\java\tools\RoiPicker.java)
Compiling 1 file in C:\Users\...a lot of jars
using the class path: C:\Users\...a lot of jars
No javax.tools.JavaCompiler available. Checking for explicit javac.
No javac.jar found (looked in C:\Fiji.app\jars)!

But I can see that in the Stitching plugins in a default Fiji installation (Stitching_-3.1.6.jar) there is already a class for ROI picker (\tools\RoiPicker.class).

My question is: can I use the existing RoiPicker.class in Fiji, so that it would appear in the menu or in the toolbar?

Best wishes,
Ales

Hello Aleš -

I am also confused by this; comments in line, below:

I am at a disadvantage here: I don’t know how to use
Plugins › Stitching › Grid/Collection stitching,
so I haven’t been able to run RoiPicker in its “native”
environment.

I can also get the RoiPicker installed in the Fiji toolbar, either
using the groovy script Curtis posted in the mailing-list thread
you linked to:

http://imagej.1557.x6.nabble.com/where-is-the-ROI-Picker-tp5018252p5018299.html

ij.IJ.runPlugIn(tools.RoiPicker.class.getName(), "")

or with this seemingly identical java plugin:

import ij.IJ;
import ij.plugin.PlugIn;
import tools.RoiPicker;

public class My_Plugin implements PlugIn {
  public void run(String arg) {
    RoiPicker rp = (RoiPicker) IJ.runPlugIn(RoiPicker.class.getName(), "");
    IJ.log (rp.getToolName());
  }
}

But, as you and Curtis note, even though the Roi Picker icon is
in the toolbar (and can be selected), I can’t get it to do anything.

Indeed, RoiPicker.class is in that jar. (And that jar is in the plugins
directory of my stock Fiji / ImageJ 1.52n installation.)

Well, yes, at least sort of. Both Curtis’s script and my plugin use it.
So the class is there, and accessible. But we can’t get it to actually
do anything.

I’ve looked with some care at the code for both RoiPicker.java
and Stitching_Grid.java.

(I assume, but don’t know, that this copy of RoiPicker.java
actually corresponds to the RoiPicker.class in my Fiji installation.)

RoiPicker.java does look like it should do something (besides
just register itself in the toolbar), and I don’t see any back-door
connections between Stitching_Grid and RoiPicker, so I
don’t see any reason that RoiPicker should only work when
run from Stitching_Grid (although I could well be missing
something).

So, in short, I agree with you: It does look like RoiPicker would
be a useful free-standing tool, and I am perplexed as to why we
can’t get RoiPicker running on its own.

Thanks, mm

Hello Aleš -

I think I know (at least some of) what is going on here.

It appears that the RoiPicker that comes with
Plugins › Stitching › Grid/Collection stitching
expects its Rois to be associated with a position in a
hyperstack (whatever that means). (I assume that
Stitching_Grid works it magic with the stuff it is
stitching being collected into a hyperstack, but I don’t
really know.)

Anyway, I have tweaked RoiPicker.java so that it also works
with “ordinary” Rois. I can run the modified version, and it
seems to do something sensible with non-hyperstack images
and ordinary Rois.

I made two modifications: First (purely for my convenience)
I commented out the package tools; statement. Second,
at line 237 of the modified file, I protected the check for
matching hyperstack positions with a
!r.hasHyperStackPosition() test. This way the
hyperstack-position check won’t be made (and won’t fail)
for Rois that aren’t associated with a hyperstack position.

This change works with ordinary images. I imagine that it
won’t break Stitching_Grids use case, but I don’t know.
(I’ve tried it both with a simple test image, and after running
Analyze > Analyze Particles ... on the “Blobs (25K)”
sample image.)

Here is the code:

// RoiPicker.java
/*
 * #%L
 * Fiji distribution of ImageJ for the life sciences.
 * %%
 * Copyright (C) 2007 - 2017 Fiji
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

// package tools;

import fiji.tool.AbstractTool;
import fiji.tool.ToolWithOptions;
import ij.IJ;
import ij.ImagePlus;
import ij.gui.ImageCanvas;
import ij.gui.ImageWindow;
import ij.gui.Roi;
import ij.plugin.frame.RoiManager;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JList;

/**
 * This tool is used to select the {@link Roi}s containing a specific x, y
 * position (obtained by a mouse click). Any Rois in the current
 * {@link RoiManager} containing that point will be added to this tool's list.
 * The Rois of the list can be itereated by repeated clicking.
 * <p>
 * NB: Known limitations to this tool:
 * </p>
 * <ul>
 * <li>When displaying a union of all matching Rois, only the first will be
 * selected in the RoiManager.</li>
 * <li>When a Roi is selected in the RoiManager, the list itself will not
 * automatically update to display that Roi.</li>
 * <li>Works best with "Show all" Rois off.
 * </ul>
 * 
 * @author Mark Hinier
 * @version 1.0 - 30 Aug 2013
 */
public class RoiPicker extends AbstractTool implements ActionListener,
	MouseListener, MouseMotionListener, MouseWheelListener, ToolWithOptions
{

	private ImagePlus imp;
	private ImageCanvas canvas;

	// Cached set of matched roi indices. Used for quick comparison
	private Set<Integer> roiSet;
	// Cached list of matched roi indices. Used for ordering iteration.
	private List<Integer> roiIndices;
	// index of the next roi to select
	int roiIndex;

	/*
	 * RUN METHOD
	 */

	@Override
	public void run(String arg) {
		super.run(arg);
	}

	/*
	 * PUBLIC METHODS
	 */

	@Override
	public String getToolName() {
		return "Roi Picker";
	}

	@Override
	public String getToolIcon() {
		return "Cee0P31f1ff3f31PC000P01785abc9678P";
	}

	/**
	 * Functional method of the RoiPicker. Determines which Rois match the x,y
	 * coordinates of the received MouseEvent and the C,Z,T coordinates of the
	 * active image. The user can cycle through these Rois with repeated clicks.
	 */
	@Override
	public void mouseReleased(MouseEvent e) {
		ImageCanvas source = (ImageCanvas) e.getSource();
		if (source != canvas) {
			// We changed image window. Update fields accordingly
			ImageWindow window = (ImageWindow) source.getParent();
			imp = window.getImagePlus();
			canvas = source;
		}
		// Convert global coords to local
		double x = canvas.offScreenXD(e.getX());
		double y = canvas.offScreenYD(e.getY());
		// Get the RoiManager
		RoiManager rm = RoiManager.getInstance();
		if (rm == null) return;

		Roi[] rois = rm.getRoisAsArray();
		// Get the active ImagePlus's current z,c,t coords
		int[] position = imp.convertIndexToPosition(imp.getCurrentSlice());

		Set<Integer> matchedSet = new HashSet<Integer>();
		List<Integer> matchedIndices = new ArrayList<Integer>();

		// generate list of all rois containing x,y and matching the ImagePlus's
		// position
		for (int i = 0; i < rois.length; i++) {
			Roi r = rois[i];
			// Check position
			if (containsPoint(r, position, x, y)) {
				// Matched. Add to the matched set and list
				Integer index = i;
				matchedSet.add(index);
				matchedIndices.add(index);
			}
		}

		// If we discovered the currently known roi set, display the next roi in
		// the series
		if (same(roiSet, matchedSet)) {
			incrementIndex();
		}
		else {
			// otherwise, update the cached indices and display the union of the rois
			roiSet = matchedSet;
			roiIndices = matchedIndices;
			roiIndex = roiIndices.size();
		}

		// Perform the roi selection
		if (matchedIndices.size() > 0) selectRois(rm);
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
//		if (!roiIndices.isEmpty()) {
//			RoiManager rm = RoiManager.getInstance();
//			if (rm == null) return;
//			incrementIndex();
//			selectRois(rm);
//		}
	}

	@Override
	public void showOptionDialog() {}

	@Override
	public void mouseDragged(MouseEvent arg0) {}

	@Override
	public void mousePressed(MouseEvent arg0) {}

	@Override
	public void mouseClicked(MouseEvent e) {}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {}

	@Override
	public void mouseMoved(MouseEvent e) {}

	@Override
	public void actionPerformed(ActionEvent e) {}

	protected void handleRecording() {}

	// for macros
	public static void select(String style, String x1, String y1, String x2,
		String y2, String width, String headLength)
	{}

	/*
	 * PRIVATE METHODS
	 */

	/**
	 * Returns true if the two provided sets are equivalent.
	 */
	private boolean same(Set<?> set1, Set<?> set2) {
		if (set1 == null || set2 == null) return set1 == set2;
		if (set1.size() != set2.size()) return false;

		for (Object r : set1) {
			if (!set2.contains(r)) return false;
		}

		return true;
	}

	// /**
	//  * Returns true iff the specified Roi contains the desired x and y positions,
	//  * and is located at the given position (C, Z, T).
	//  */
	// private boolean containsPoint(Roi r, int[] position, double x, double y) {
	// 	return r.getCPosition() == position[0] && r.getZPosition() == position[1] &&
	// 		r.getTPosition() == position[2] &&
	// 		r.contains((int) Math.floor(x), (int) Math.floor(y));
	// }

	/**
	 * Returns true iff the specified Roi contains the desired x and y positions,
	 * and the Roi has no hyperstack position or is located at the given position (C, Z, T).
	 */
	private boolean containsPoint(Roi r, int[] position, double x, double y) {
	        boolean contains = !r.hasHyperStackPosition()  ||  r.getCPosition() == position[0] && r.getZPosition() == position[1] && r.getTPosition() == position[2];
	        return  contains  &&  r.contains((int) Math.floor(x), (int) Math.floor(y));
	}

	/**
	 * Selects the {@link #roiIndex} Roi from the list of matched
	 * {@link #roiIndices}, within the specified RoiManager. Cycles through each
	 * roi in the list, starting with a group select of all matching rois.
	 */
	private void selectRois(RoiManager rm) {
		if (roiIndices.size() == 1) {
			rm.select(roiIndices.get(0));
			IJ.showStatus("1 of 1 Rois selected.");
		}
		else if (roiIndex < roiIndices.size()) {
			rm.select(roiIndices.get(roiIndex));
			IJ.showStatus((roiIndex + 1) + " of " + roiIndices.size() +
				" Rois selected. Click to cycle...");
		}
		else {
			rm.select(roiIndices.get(0));
			for (int i = 1; i < roiIndices.size(); i++) {
				rm.select(roiIndices.get(i), true, false);
			}
			IJ.showStatus(roiIndices.size() + " overlapping Rois. Click to cycle...");
		}

		// Updates the displayed list of Rois to ensure the currently selected Roi
		// is visible.
		try {
			Field listField = RoiManager.class.getDeclaredField("list");
			listField.setAccessible(true);
			int scrollIndex = roiIndex == roiIndices.size() ? 0 : roiIndex;
			final JList list = (JList) listField.get(rm);
			list.ensureIndexIsVisible(roiIndices.get(scrollIndex));
		}
		catch (SecurityException e) {
			IJ.error("RoiPicker: SecurityException when updating RoiManager.");
		}
		catch (NoSuchFieldException e) {
			IJ.error("RoiPicker: NoSuchFieldException when updating RoiManager.");
		}
		catch (IllegalArgumentException e) {
			IJ.error("RoiPicker: IllegalArgumentException when updating RoiManager.");
		}
		catch (IllegalAccessException e) {
			IJ.error("RoiPicker: IllegalAccessException when updating RoiManager.");
		}

	}

	/**
	 * Bumps the {@link #roiIndex}, or resets to 0 if == {@link #roiIndices} size
	 * + 1. The extra index indicates multi-roi selection.
	 */
	private void incrementIndex() {
		roiIndex = (roiIndex + 1) % (roiIndices.size() + 1);
	}

}

Also, I have a simple plugin that installs the modified RoiPicker
tool:

// RoiPicker_Install.java
import ij.IJ;
import ij.ImagePlus;
import ij.gui.Roi;
import ij.plugin.PlugIn;
import ij.plugin.frame.RoiManager;

public class RoiPicker_Install implements PlugIn {
  public void run(String arg) {
    // install RoiPicker
    RoiPicker rp = (RoiPicker) IJ.runPlugIn(RoiPicker.class.getName(), "");
    
    // make a sample image
    ImagePlus imp = IJ.createImage ("rois", "8-bit ramp", 256, 256, 1);
    imp.show();

    // add some rois
    RoiManager rm = new RoiManager();
    rm.addRoi (new Roi (50, 50, 50, 50));
    rm.addRoi (new Roi (75, 75, 50, 50));
    rm.addRoi (new Roi (150, 150, 50, 50));
    rm.moveRoisToOverlay (imp);
  }
}

If you run Plugins > RoiPicker Install, it will install
the RoiPicker tool, and, for convenience, it will also create
a sample image with some Rois.

(After compiling the two source files, I place RoiPicker.class
and RoiPicker_Install.class in a jar file, RoiPicker_Install.jar,
and copy it to my plugins directory, so that RoiPicker Install
will show up in my Plugins menu.)

Thanks, mm

2 Likes

Hi @mountain_man, thank you for the fix!

Your modified RoiPicker works, I can select ROIs by clicking on them, excellent! :smile:

I saved the .java files outside Fiji, then installed them using Plugins/Install... , I got a couple of compiling errors, but the plugin was installed. I can find and run RoiPicker using the search field in the status bar - the toolbar icon is added, and it works, I can select ROIs by clicking on the image.

Then I searched for .class files of both plugins and packed them in a .jar file as you described above and the RoiPicker_Install command from the menu now installs the RoiPicker button without errors.

I don’t know if RoiPicker is still used in the Stitching plugins - I use them a lot, but all the methods are automatic now, so perhaps the RoiPicker is there only as a remnant from the past.

Thanks again, I really appreciate your help!

Cheers, Aleš

1 Like

Hi, sorry, it is an old tread, but i really need to use ROI picker. i have tried to repeat what you did (save separately RoiPicker.java and RoiPicker_Install.java, then “Install Plugin” in FIJI (is order of installation critical?) but no luck. Does it still work for you?
Thank you!

1 Like