Programmatically Select ROIs in each Slice using JAVA

Hi,

Using Java, I’m trying to figure out how to select all ROIs for a given channel, combine them together, and then add that new combined ROI into the ROI Manager.

I’d then like to rename that combined ROI something like “1_Summary” for the channel 1 combined/summary ROI, and “2_Summary” for the channel 2 combined/summary ROI, etc.

I looked at this post: Define a "class/category" property for ROI?
And the setGroup and selectGroup methods detailed here seem useful. The RoiManager API also lists them (https://imagej.nih.gov/ij/developer/api/ij/plugin/frame/RoiManager.html), but these methods don’t seem available in my code. Maybe I’m using a different version of ImageJ?

Heres my code so far. It works for one channel, but if you begin selecting multiple ROIs from different channels, it incorrectly merges all of the ROIs from every channel together into the Summary ROIs, and then each Summary ROI ends up being identical.

import org.scijava.command.Command;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import ij.IJ; //this is for debugging method
import ij.ImagePlus;
import ij.gui.Roi;
import ij.gui.WaitForUserDialog;
import ij.plugin.frame.RoiManager;

import net.imagej.ImageJ;

public class roimanager_fun implements Command {
	@Override
	public void run() {		
	
	RoiManager rm = new RoiManager();
	
	//tell ROI manager to associate rois with channels/slices. May be useful. Not sure
	rm.runCommand("Associate", "true");
	
	ImagePlus imp = IJ.openImage();
	imp.show();		
	
	// ask user to select ROIs		
	WaitForUserDialog wd_roi= new WaitForUserDialog("USER ROI SELECTION","select Rois");
	wd_roi.show();
	
	Roi[  ] rois = rm.getSelectedRoisAsArray();
	//convert to List so we can use indexOf(roi) further below
	List<Roi> rois_list  = Arrays.asList(rois);     
	
	
	//Key is Channel/Slice Number. Value is a set of all rois associated with that channel
	HashMap<Integer, HashSet<Roi>> map = new HashMap<Integer, HashSet<Roi>>();
	
	
	//loop through the roiset and match each roi with its corresponding channel
	for (Roi roi : rois_list ) {
		
	Integer channel  = 	rm.getSliceNumber(roi.getName());
	
 if ( map.containsKey(channel))
	 map.get(channel).add(roi);
 
 else {
	 
	 HashSet<Roi> set = new HashSet<>();
	 map.put(channel, set);
	 set.add(roi);
 }
	
		}
	
		/*
		 * Now that we have HashMap linking each channel to its Rois, we can select all the
		 * ROIs associated with each channel. Then we can combine those ROIs into a summary ROI, and
		 * add that Summary ROI to  the ROImanager
		 */	
	
	for (HashMap.Entry<Integer, HashSet<Roi>> entry : map.entrySet()) {
	    Integer channel_key = entry.getKey(); //get the channel
	    HashSet<Roi> roi_set = entry.getValue(); //get all the Rois for this channel
	    
	    //select this roi in the roimanager
	    for (Roi roi : roi_set) {
	    	
	    	int index = rois_list.indexOf(roi);
	    	
	    	
	    	//not sure what needs to be true. On mac I hold down the command key to select multiple ROIs
	    	rm.select(index, true, true);
	    	
	    	
	    }
	    
	    //Now that we've selected all the ROIs for this channel, combine them together and add the new Combined/Summary ROI for this channel
		
	    //select the indexes associated with the current channel
		rm.setSelectedIndexes( rm.getSelectedIndexes() );
		
		
		rm.runCommand(imp, "Combine"); //combine all of our selected ROIs into one summary ROI
		
		rm.runCommand(imp, "add");
		
		
			//rename the combined ROI as channel#_summary: e.g. 1_Summary, for the combined channel 1 ROI
			  Roi[ ] updated_rois = rm.getSelectedRoisAsArray();
			  
			  rm.rename(updated_rois.length - 1, channel_key.toString() + "_" +
			  "Summary_ROI");
			 
		
		
		//deselect all selected ROIs so we can repeat for the next channel with a a clean slate.
		rm.runCommand(imp, "Select None");
		
		rm.runCommand(imp, "Deselect");
		
		
	    
	    
	}
	
	
	
	
	
	}
	

	public static void main(String[] args) {
		// set the plugins.dir property to make the plugin appear in the Plugins menu
		
		final ImageJ ij = new ImageJ();
		ij.launch(args);


		// Launch the command right away.
		ij.command().run(roimanager_fun.class, true);
		
	}
	
}

I can’t help but think that there is already a built in way to associate ROIs with channels/slices? I know there is rm.runCommand(“Associate”, “true”); but I’m not sure how to use this programmatically.

Hello,

This is a recent addon so you might need to update to the last version or even daily build version of ImageJ to get the new method.
However it is more to define ROI categories like object classes, that may exist on the same image.
I think for what you want to do, using the slice attribute of the ROI as you do is the good option.

I looked at the code, but I am more use to Jython so I was a bit over-whelmed.
I would suggest a simpler approach, here in pseudo-code

listCompositeRoi = [] # this list will contain the merged ROI for each channel ie len(list)=nSlice
for i in range(Nslices):
	listRoi = [] #this list will contain the ROI for the given slice (overwritten for every new slice)
  
	for roi in RoiManager:
		if roi.sliceIndex == i:
		listRoi.append(roi) 

	# Once we collected all ROI for that slice we combine them  
	compositeRoi = combine(listRoi)
	
	# Add the composite ROI to the main list
	listCompositeRoi.append(compositeROI)

Hope it helps, good luck !

2 Likes

Hi @LThomas,

Thanks so much for posting! The pseudocode you posted really helped clean up my code. I also didnt realize that the ROI objects already contained information about their channel/slice of origin through roi.getCPosition()

Unfortunately I’m still getting the same bug. I’m almost certain that the bug is due to me not deselecting/re-initializing my channel-specific selected ROIs after each pass of the outer for loop. This is despite me reinitializing my list of selected ROIs in each pass of the outer for loop and deselecting all the ROIs after each completion of the outer for loop. Heres the relevant part of my implementation:


    int[] CompositeRois = new int[imp.getNChannels()]; //this array will contain the merged ROI for each channel i.e. size = getNChannels. May use this later on
	
    Roi [] selected_rois  = rm.getRoisAsArray(); //this array stores the ROIs we have already selected
    
	for (int channel = 0; channel < imp.getNChannels(); channel++) {
		

		
		List<Integer> roiIndexes = new ArrayList<Integer>(); //this array will contain the ROI indexes for the given channel (overwritten for every new channel)
		  
		for (Roi roi : selected_rois) {
			
			if (roi.getCPosition() == channel) {   roiIndexes.add(rm.getRoiIndex(roi)); }
			
			
			
		}
		
		//Now convert the ArrayList of Integers to an int array. Need to do this before we use setSelectedIndexes method
		int[] roiArr = roiIndexes.stream().mapToInt(Integer::intValue).toArray();
		
		if (roiArr == null) { ; } //if there were no ROIs for this channel, pass
		
		else if (roiArr.length == 0) { ; } //similar check. if there were no ROIs for this channel, pass
		
		else { //if there are ROIs for this channel, //select them, combine them, and add the combined ROI to the ROIManger
	
		rm.setSelectedIndexes(roiArr);
		
		rm.runCommand(imp, "Combine");
		
		rm.runCommand(imp, "Add");
		
		//Do everything possible to clear the selections in the ROI manager. So we can restart with clean slate. NOT WORKING!
		
		rm.runCommand(imp, "Deselect");
		
		rm.runCommand(imp, "Select None");
		
		rm.run("Select None");
		
		}
		
	}
	

Oddly, the ROI manager keeps an ever growing list of selected ROIs. Here’s a commented traceback from the command recorder after I have run this code. Notice how the number of selected ROIs keeps on growing, instead of deselecting the ROIs for each channel:

imp.setRoi(1008,471,402,513); //select and add 1st ROI to channel 1
rm.addRoi(imp.getRoi()); 
imp.setRoi(1491,1512,393,465); //select and add 2nd ROI to channel 1
rm.addRoi(imp.getRoi());
imp.setRoi(1473,414,504,417); //select and add 1st ROI to channel 2
rm.addRoi(imp.getRoi());
imp.setRoi(582,1926,435,408); //select and add 2nd ROI to channel 2
rm.addRoi(imp.getRoi());

//Below we have the ever growing list of selected ROIs. Note how it grows 4 times, once for each channel
rm.select(0);
rm.setSelectedIndexes(new int[]{0,1});
rm.setSelectedIndexes(new int[]{0,1,2});
rm.setSelectedIndexes(new int[]{0,1,2,3});

I think the key is deselecting the ROIs after each pass of the outer for loop but I can’t seem to manage that.

Here’s the full code in case anyone wants to run it on their machine:



import org.scijava.command.Command;

import java.util.ArrayList;

import java.util.List;




import ij.IJ; //this is for debugging method
import ij.ImagePlus;
import ij.gui.Roi;
import ij.gui.WaitForUserDialog;
import ij.plugin.frame.RoiManager;




import net.imagej.ImageJ;

public class Broi_managerfun implements Command {




	@Override
	public void run() {		
	
	RoiManager rm = new RoiManager();
	
	//tell ROI manager to associate rois with channels/slices. May be useful. Not sure
	rm.runCommand("Associate", "true");
	
	ImagePlus imp = IJ.openImage();
	imp.show();		
	
	// ask user to select ROIs		
	WaitForUserDialog wd_roi= new WaitForUserDialog("USER ROI SELECTION","select Rois");
	wd_roi.show();
	
		/* Relevant problem solving code */
	
    int[] CompositeRois = new int[imp.getNChannels()]; //this array will contain the merged ROI for each channel i.e. size = getNChannels. May use this later on
	
    Roi [] selected_rois  = rm.getRoisAsArray(); //this array stores the ROIs we have already selected
    
	for (int channel = 0; channel < imp.getNChannels(); channel++) {
		

		
		List<Integer> roiIndexes = new ArrayList<Integer>(); //this array will contain the ROI indexes for the given channel (overwritten for every new channel)
		  
		for (Roi roi : selected_rois) {
			
			if (roi.getCPosition() == channel) {   roiIndexes.add(rm.getRoiIndex(roi)); }
			
			
			
		}
		
		//Now convert the ArrayList of Integers to an int array. Need to do this before we use setSelectedIndexes method
		int[] roiArr = roiIndexes.stream().mapToInt(Integer::intValue).toArray();
		
		if (roiArr == null) { ; } //if there were no ROIs for this channel, pass
		
		else if (roiArr.length == 0) { ; } //similar check. if there were no ROIs for this channel, pass
		
		else { //if there are ROIs for this channel, //select them, combine them, and add the combined ROI to the ROIManger
	
		rm.setSelectedIndexes(roiArr);
		
		rm.runCommand(imp, "Combine");
		
		rm.runCommand(imp, "Add");
		
		//Do everything possible to clear the selections in the ROI manager. So we can restart with clean slate. NOT WORKING!
		
		rm.runCommand(imp, "Deselect");
		
		rm.runCommand(imp, "Select None");
		
		rm.run("Select None");
		
		}
		
	}
	
	
	}
	public static void main(String[] args) {
		// set the plugins.dir property to make the plugin appear in the Plugins menu
		
		final ImageJ ij = new ImageJ();
		ij.launch(args);


		// Launch the command right away.
		ij.command().run(Broi_managerfun.class, true);
		
	}
	
}

I figured it out! The key was that roi.getCPosition wasn’t doing what I thought it was doing. It was always returning 0. I changed to roi.getZPosition() and now the code works just as expected. ( I’m still a little confused as to the difference between these two functions. The images I am working with are not Z-Stacks. They are single focal plane 4 color/channel images. )

Here’s the updated code in case anyone finds it useful. It goes through all your ROIs, selects The ROIs from the same slice/channel, and then combines them and adds that combined ROI to the ROI manger.

 Roi [] CompositeRois = new Roi [imp.getNChannels()]; //this array will contain the merged ROI for each channel i.e. size = getNChannels. May use this later on
	
    Roi [] selected_rois  = rm.getRoisAsArray(); //this array stores the ROIs we have already selected
    
    
    
	for (int channel = 1 ; channel <= imp.getNChannels(); channel++) {
		
		
		
		List<Integer> roiIndexes = new ArrayList<Integer>(); //this array will contain the ROI indexes for the given channel (overwritten for every new channel)
		  
		for (Roi roi : selected_rois) {
			
			
			if (roi.getZPosition() == channel) {   roiIndexes.add(rm.getRoiIndex(roi)); }
			
			
			
		}
		
		//Now convert the ArrayList of Integers to an int array. Need to do this before we use setSelectedIndexes method
		int[] roiArr = roiIndexes.stream().mapToInt(Integer::intValue).toArray();
		
		if (roiArr == null) { ; } //if there were no ROIs for this channel, pass
		
		else if (roiArr.length == 0) { ; } //similar check. if there were no ROIs for this channel, pass
		
		else { //if there are ROIs for this channel, //select them, combine them, and add the combined ROI to the ROIManger
	
		rm.setSelectedIndexes(roiArr);
		
		rm.runCommand(imp, "Combine");
		
		rm.runCommand(imp, "Add");
		
		//Do everything possible to clear the selections in the ROI manager. So we can restart with clean slate.
		
		rm.runCommand(imp, "Deselect");
		
		rm.runCommand(imp, "Select None");
		
		rm.run("Select None");
		
		}

Well done !
I think sometimes the dimensions are not recognized properly (Z instead of C…).
You can reorder them if you want, by using Image > Hyperstack > Re-order hyperstack
Even if you have a simple stacj it should work by inverting the dimensions.