roiManager: How to fill a selection based on a variable with user-defined levels?

Hello !
I am currently working on an ImageJ macro to help me analyze quadrats pictures taken from underwater marine habitats. Briefly put, so far I’ve managed to create a macro that divides the image into squared selections forming a grid based on a number of row and column defined by the user. Then, for each selection, the user is asked to enter a category that defines what he is seeing (for example sand, shell, algae). These categories are not previously defined so I have no way to know how many there will be. Besides, the categories might change between pictures.
I’ve managed to create an array where the category gets saved in for each selection. However, if several selections have the same category (which I know will happen), it means my array is filled with duplicates.

Let’s say I have 4 selections and that the array mentionned above contains “shell, algae, gravel, algae”. What I would like to do is to have each selection filled with a color based on its category. For example, to have all the selections categorised as “algae” filled in green.

I tried using what was suggested in this answer (Random color generator - #4 by anon96376101 ) but it considers duplicates as different values (two cells categorized as ‘algae’ will have two different colors).

So far, all I’ve think about that does not imply to have the categories defined in the macro would be to open a dialog box asking the user to attribute a number (so a group) to each category but it would mean that the user also needs to remember all the group/category combinaisons while processing the image but also when processing the next one so that the colors remain the same between images for the same category .

I am completely new to creating imageJ macro but the macro recorder has been really helpful so far along with different type of documentations found online. However, some additionnal help regarding this group/category problem would be greatly appreciated! I am not even sure that what I am trying to do is actually possible… Any suggestions or function I could look into would be a great help! :slight_smile:

Thank you !

Update: In addition to the array that contains all the categories (I called it catArray), I have created an array where all the duplicates from catArray are removed (I called it OutputArray). Then, the user is asked to choose a color for each level of OutputArray and the result is saved in an array called colArray.
I was thinking about using these three arrays to determine how my selection should be filled but I’m not sure how to write my code. I tried this but it fills all my selection in blue…

catArray=newArray("shell","algae","gravel","algae"); // example of catArray
OutputArray=newArray("shell","algae","gravel"); //example of OutputArray
colArray=newArray("red","green","purple"); //example of colArray

for(h=0;h<roiManager("Count");h++){
	roiManager("select",h);
	for(k=0;k<catArray.length;k++){
		for(j=0;j<OutputArray.length;j++){
			for(i=0;i<colArray.length;i++){
				if (catArray[k]==OutputArray[j]){
					roiManager("Set Fill Color",colArray[i]);
				}	
			}
		}
	}
}

Again, any suggestion on how to solve this would be greatly appreciated !

Hi Ade,

Something like this? It works with four ROIs.

CatArray=newArray("shell","algae","gravel","algae");
SortedCarArray=Array.copy(CatArray);

//sorting array and getting rid of duplicates
SortedCatArray=Array.sort(SortedCarArray);

OutputArray=newArray(SortedCatArray[0]);

for (i=0; i<SortedCatArray.length-1; i++){  
            if (SortedCatArray[i] != SortedCatArray[i+1]){
                OutputArray=Array.concat(OutputArray,SortedCatArray[i+1]); 
            }   
	}
	
// creating a numerical array the same lenght as the array containing the individual categories
NumericalArray=Array.getSequence(OutputArray.length);

// Create Colour array where we're replacing each entry in CatArray with a number from NumericalArray

ColourArray=newArray();

for(j=0; j<CatArray.length; j++){
	for(k=0; k<NumericalArray.length; k++){
		if(CatArray[j]==OutputArray[k]){
			ColourArray=Array.concat(ColourArray,NumericalArray[k]);
		}
	}
	
}


// use the numbers in ColourArray which are associated with their ROI to assign a colour to the ROI when looking at the split image using the Glasbey LUT

for(h=0; h<roiManager("Count");h++){
	roiManager("Select",h);
	
	setForegroundColor(ColourArray[h]+1,0,0);
	run("Fill", "slice");
	}
	
run("Split Channels");
selectWindow("Untitled (red)"); //you'll need to modify this so the software selects the right image...
run("glasbey_on_dark");
//you may want to change the image as RGB or keep it as it is 

Sincerely,

Matthieu

1 Like

Hello Matthieu!
Thank you for your time and all the comments you added to your code to explain exactly what you were doing!
The part where the duplicates are removed is so much simpler and faster than what I was doing (based on what I had found online and using Jython):

//Remove duplicates from catArray
function unique(InputArray) {
    separator = "\\n"
    
    InputArrayAsString = InputArray[0];
    for(i = 0; i < InputArray.length; i++){
        InputArrayAsString += separator + InputArray[i];
    }
    
    script = "result = r'" + 
             separator +
             "'.join(set('" +
             InputArrayAsString +
             "'.split('" + 
             separator +
             "')))";
    
    result = eval("python", script);
    OutputArray = split(result, separator);
    return OutputArray;
}

InputArray=catArray;
OutputArray = unique(InputArray);
Array.show(OutputArray);

Your code seems to work perfectly except for the last part where I had to replace run("glasbey_on_dark"); by run("Glasbey"); because I had an error message popping up when using the first one. I’m not familiar with these two functions, do you think it will change something? After a quick look on the internet, I saw it was available from Image > Lookup Tables and I realised I only have Glasbey listed here.

I will try to implement your code in my largest code where the grid selection is created and with a bigger number of selections (I haven’t decided yet but it will most likely be 100 or 200 by picture). I’ll let you know how it went :slight_smile:

Thanks again for your help!

1 Like

Update:
So apparently the code is running well, I have no error message popping up, however, I’ve noticed something odd in ColourArray: When I tried using my squared selections from my grid (I tried small with only 4 selections), I realised that a fifth value was being added in ColourArray at the very beggining (the 0). Because of that, the selections are not filled correctly… Here is a screen capture of all the output, including the different array that are created along the way:

.

What is currently filled in green should be of two different colour as they have two different categories (maerl and gravel form my example). This problem didn’t happen when I copy/pasted and run you code with 4 random selections :

So I guess it must come from my own previous code but I don’t know where the problem could be considering that all the others arrays are perfectly fine…
Here is what I have done so far with your code at the end:

dir=getDirectory("Choose a Directory");
waitForUser("Please choose an image");
open();

//Grid creation	
setTool("point");
waitForUser("Please draw the upper left corner inside the quadrat. Press ok when finished");
getSelectionCoordinates(xPoints,yPoints);
x = xPoints[0];
y = yPoints[0];

setTool("point");
waitForUser("Please draw the bottom right corner inside the quadrat. Press ok when finished");
getSelectionCoordinates(xPoints,yPoints);
x2 = xPoints[0];
y2 = yPoints[0];

Dialog.create("Grid size");
Dialog.addMessage("What is the size of the grid?");
Dialog.addNumber("Rows:","10");
Dialog.addNumber("Colums:","10");
Dialog.show();
numRow = Dialog.getNumber; //how many rows
numCol = Dialog.getNumber; //how many columns
width = (x2-x)/numRow; //the width of the cell (pixel) 
height = (y2-y)/numCol; //the height of the cell (pixel)
spacing = 0; //spacing between the cells

//Will create as many selection as numRow*numCol add them to the ROI Manager
for (g = 0; g < numRow; g++) {
    for (j = 0; j < numCol; j++) {
        xOffset = j * (width + spacing);
        yOffset = g * (height + spacing);
        makeRectangle(x + xOffset, y + yOffset, width, height);
        roiManager("Add");
		run("Labels...", "font=18 show draw");
		run("Add Selection...");
		run("Overlay Options...","stroke=yellow width=3 fill=none show"); 
    }
}
roiManager("Show All with labels");

setOption("ExpandableArrays",true);
CatArray=newArray;

//For each cell, enter a category
for(h=0;h<roiManager("Count");h++){
	roiManager("select",h);
	run("Labels...", "font=18 show");
		
	//Enter the category:
	Dialog.create("Observations");
	Dialog.addMessage("Please enter the name of the category:");
	Dialog.addString("Category:","Please enter a category");
	Dialog.show();
	category=Dialog.getString();
	
	CatArray[h]=category;
	
	name=RoiManager.getName(h);
	hbis=h+1; //number of each square
	roiManager("Rename",hbis+"_"+name+"_"+category);
	}

//Fill based on category
SortedCarArray=Array.copy(CatArray);

//sorting array and getting rid of duplicates
SortedCatArray=Array.sort(SortedCarArray); //Class A->Z
Array.show(SortedCatArray);

OutputArray=newArray(SortedCatArray[0]);

//Remove duplicates
for (i=0; i<SortedCatArray.length-1; i++){  
            if (SortedCatArray[i] != SortedCatArray[i+1]){
                OutputArray=Array.concat(OutputArray,SortedCatArray[i+1]); 
            }   
	} 


// creating a numerical array the same lenght as the array containing the individual categories
NumericalArray=Array.getSequence(OutputArray.length);

// Create Colour array where we're replacing each entry in CatArray with a number from NumericalArray
for(j=0; j<CatArray.length; j++){
	for(k=0; k<NumericalArray.length; k++){
		if(CatArray[j]==OutputArray[k]){
			ColourArray=Array.concat(ColourArray,NumericalArray[k]);
		}
	}
	
}

// use the numbers in ColourArray which are associated with their ROI to assign a 
//colour to the ROI when looking at the split image using the Glasbey LUT
for(h=0; h<roiManager("Count");h++){
	roiManager("Select",h);
	setForegroundColor(ColourArray[h]+1,0,0);
	run("Fill", "slice");
	}

run("Split Channels");
selectWindow(name+" (red)");
run("Glasbey");

Hi Ade,

The issue is with ColourArray: it’s not defined when concatenated with something from NumericalArray, so Fiji assigns it a value (0).

So, I’ve changed the code to this:

dir=getDirectory("Choose a Directory");
waitForUser("Please choose an image");
open();
Title=getTitle();
roiManager("reset");

//Grid creation	
setTool("point");
waitForUser("Please draw the upper left corner inside the quadrat. Press ok when finished");
getSelectionCoordinates(xPoints,yPoints);
x = xPoints[0];
y = yPoints[0];

setTool("point");
waitForUser("Please draw the bottom right corner inside the quadrat. Press ok when finished");
getSelectionCoordinates(xPoints,yPoints);
x2 = xPoints[0];
y2 = yPoints[0];

Dialog.create("Grid size");
Dialog.addMessage("What is the size of the grid?");
Dialog.addNumber("Rows:","10");
Dialog.addNumber("Colums:","10");
Dialog.show();
numRow = Dialog.getNumber; //how many rows
numCol = Dialog.getNumber; //how many columns
width = (x2-x)/numRow; //the width of the cell (pixel) 
height = (y2-y)/numCol; //the height of the cell (pixel)
spacing = 0; //spacing between the cells

//Will create as many selection as numRow*numCol add them to the ROI Manager
for (g = 0; g < numRow; g++) {
    for (j = 0; j < numCol; j++) {
        xOffset = j * (width + spacing);
        yOffset = g * (height + spacing);
        makeRectangle(x + xOffset, y + yOffset, width, height);
        roiManager("Add");
		run("Labels...", "font=18 show draw");
		run("Add Selection...");
		run("Overlay Options...","stroke=yellow width=3 fill=none show"); 
    }
}
roiManager("Show All with labels");

setOption("ExpandableArrays",true);
CatArray=newArray;

//For each cell, enter a category
for(h=0;h<roiManager("Count");h++){
	roiManager("select",h);
	run("Labels...", "font=18 show");
		
	//Enter the category:
	Dialog.create("Observations");
	Dialog.addMessage("Please enter the name of the category:");
	Dialog.addString("Category:","Please enter a category");
	Dialog.show();
	category=Dialog.getString();
	
	CatArray[h]=category;
	
	name=RoiManager.getName(h);
	hbis=h+1; //number of each square
	roiManager("Rename",hbis+"_"+name+"_"+category);
	}

//Fill based on category
SortedCatArray=Array.copy(CatArray);

//sorting array and getting rid of duplicates
SortedCatArray=Array.sort(SortedCatArray); //Class A->Z
Array.show(SortedCatArray);

OutputArray=newArray(SortedCatArray[0]);

//Remove duplicates
for (i=0; i<SortedCatArray.length-1; i++){  
            if (SortedCatArray[i] != SortedCatArray[i+1]){
                OutputArray=Array.concat(OutputArray,SortedCatArray[i+1]); 
            }   
	} 


// creating a numerical array the same lenght as the array containing the individual categories
NumericalArray=Array.getSequence(OutputArray.length);
ColourArray=newArray();

// Create Colour array where we're replacing each entry in CatArray with a number from NumericalArray
for(number=0; number<CatArray.length; number++){
	for(k=0; k<NumericalArray.length; k++){
		if(CatArray[number]==OutputArray[k]){
			ColourArray=Array.concat(ColourArray,NumericalArray[k]);
		}
	}
	
}

// use the numbers in ColourArray which are associated with their ROI to assign a 
//colour to the ROI when looking at the split image using the Glasbey LUT
for(Region=0; Region<roiManager("Count");Region++){
	roiManager("Select",Region);
	setForegroundColor(ColourArray[Region]+1,0,0);
	run("Fill", "slice");
	}

run("Split Channels");
selectWindow(Title+" (red)");
run("Glasbey");

I’ve noticed that you use ImageJ instead of Fiji, which may explain why you don’t have Glasbey on dark.

There’s also an issue with the way you define your ROIs in the macro: if the rectangle you define at the beginning is not divisible by the number of rows or columns (i.e. you don’t have an integer), the software creates ROIs that are outside of the defined rectangle. Try to run your macro with 3 rows or 3 columns and you’ll see what I mean. You’ll have to work on this.

1 Like

Thank you again for your time! I greatly appreciate your help!
I should have noticed the issue with ColourArray, sorry. Also thank you for pointing out about the problem with the ROIs creation. So far I’ve only used it with numRow=numCol because that is what I need so I had no idea there was an issue when numRow=/=numCol. I will work on that to fix it.

Following your advice I downloaded Fiji instead. I don’t know why but I guess because the name is different I thought it was another software.

Thanks to you I should be fine with my macro once this ROIs problem is settled. Thanks!

Ade

1 Like

Hi Ade,

I’ve worked out the issue with the ROIs: j and g are swapped when calculating the offsets. The code should be:

for (g = 0; g < numRow; g++) {
    for (j = 0; j < numCol; j++) {
        xOffset =g * (width + spacing);
        yOffset = j * (height + spacing);
        makeRectangle(x + xOffset, y + yOffset, width, height);
        roiManager("Add");
		run("Labels...", "font=18 show draw");
		run("Add Selection...");
		run("Overlay Options...","stroke=yellow width=3 fill=none show"); 
    }
}

All the best,

Matthieu

1 Like

Hello Matthieu!

I know I’ve said it before but thank you again! I’ve been trying to figure out where was the issue but I thought it was in the previous paragraph…
I corrected my script and it works perfectly now! It is the first time that I use this forum to seek help but I will definitely do it again if need be if everyone is as nice and helpful as you!

All the best,
Ade