Best way to objectively select area of interest using images with different levels of brightness

imagej

#1

Hi All,

I’ve been struggling with post-processing images from a study. Hopefully someone can help!

I attached two sets of images (A and B). Both sets show one image with a fully contaminated (blue color) surface, and another image of a partially cleaned surface (residual contamination in blue). The difference between the two images is the level of brightness.

My goal is to objectively measure the area of contamination (in blue) without subjectively influencing the data. Ideally, a global threshold would be optimal, but all of the preset filters give inaccurate results across the entire set of data. Is there a way to objectively improve the images so that I could continue threshold by Image>Adjust>Color Threshold?

Background:
The blue color on the surfaces is UV ink under a black light. I am measuring the change in contamination for a particular area of this surface, so I used the polygon tool to trace, measure (Analyze>Measure), and clear outside/Mask (Edit>Clear Outside) the image. I am using the Image>Adjust>Color Threshold to adjust the brightness for selection, and measuring the area that’s perceived to have contamination (Analyze>Measure).


#2

@adamczibur

So… just a quick test - but you can try this code snippet and see if that fits what you are aiming for:

// have the original image opened
originalImage = getImageID();
run("Duplicate...", " ");
run("8-bit");
setAutoThreshold("Mean dark");
//run("Threshold...");
run("Create Mask");
run("Analyze Particles...", "exclude add");
selectImage(originalImage);
roiManager("Select", 0); // overlay ROI on original Image

// Color Thresholder 2.0.0-rc-67/1.51t
// Autogenerated macro, single images only!
min=newArray(3);
max=newArray(3);
filter=newArray(3);
a=getTitle();
run("RGB Stack");
run("Convert Stack to Images");
selectWindow("Red");
rename("0");
selectWindow("Green");
rename("1");
selectWindow("Blue");
rename("2");
min[0]=0;
max[0]=255;
filter[0]="pass";
min[1]=0;
max[1]=255;
filter[1]="pass";
min[2]=40;
max[2]=255;
filter[2]="pass";
for (i=0;i<3;i++){
  selectWindow(""+i);
  setThreshold(min[i], max[i]);
  run("Convert to Mask");
  if (filter[i]=="stop")  run("Invert");
}
imageCalculator("AND create", "0","1");
imageCalculator("AND create", "Result of 0","2");
for (i=0;i<3;i++){
  selectWindow(""+i);
  close();
}
selectWindow("Result of 0");
close();
selectWindow("Result of Result of 0");
rename(a);
// Colour Thresholding-------------

In the end - you are given a mask that you can then calculate the area of, etc. Basically - I used the image you provided to automatically select the region of interest and then overlaid that onto your RGB image and measured the ‘blue’ signal within using Color Threshold. The choosing of the Color Threshold settings is a bit ‘arbitrary’ - right?! But as long as you can justify using those particular settings and use the same for all images acquired using the exact same settings… you should be safe.

And you can modify that code snippet I gave you - and potentially run it on multiple images in files, etc. Here are some helpful links that will help get you started in that direction with scripting:

I hope this helps and that I didn’t just murky the water! If you have more questions - ask! We are all here to help and perhaps others have different/better options for you, etc. but at least this is a start.

eta


#3

@etarena Hi Ellen,

Thank you for the quick response! I’ll give the code a shot. I haven’t used a macro on ImageJ yet - I’m fairly new to the program.
I’ll also look at the attached links to learn more.

Further detail on the study
I am doing a simple percentage calculation of the area in blue to the total area of the surface.
(e.g. 25% of the surface has blue contamination on it)

Furthermore, the images I provided are not the original images, they are cropped image of the focus area. I traced the focus area with the polygon tool, measured the area I traced, then used the Edit>clear outside function to give you the provided images. I used the Color Threshold feature to trace the areas in blue. By arbitrary Color Threshold settings, are you referring to the use of the Color Threshold tool or the Brightness Parameters to threshold with?

Example of the current results (using Color Threshold to select the number of pixels) - Using set A.
Fully Soiled:
Total area (area of surface) - 1079396
Color Threshold area - 998071
Percentage - 92%

Cleaning Attempt:
Total area (area of surface) - 1068828
Color Threshold area - 84312
Percentage 8%

If it’s not too much to ask, would you be able to send me your results (image and pixel area of interest)?
I’d like to make sure I am seeing what you’re seeing.

Thanks again!
Adam


#4

So… @adamczibur

I updated the code now then…

// have the original image opened
originalImage = getImageID();
run("Duplicate...", " ");
run("8-bit");
setAutoThreshold("Mean dark");
//run("Threshold...");
run("Create Mask");
run("Analyze Particles...", "size=100-Infinity display exclude add");
selectImage(originalImage);
roiManager("Select", 0); // overlay ROI on original Image

// Color Thresholder 2.0.0-rc-67/1.51t
// Autogenerated macro, single images only!
min=newArray(3);
max=newArray(3);
filter=newArray(3);
a=getTitle();
run("HSB Stack");
run("Convert Stack to Images");
selectWindow("Hue");
rename("0");
selectWindow("Saturation");
rename("1");
selectWindow("Brightness");
rename("2");
min[0]=0;
max[0]=255;
filter[0]="pass";
min[1]=0;
max[1]=255;
filter[1]="pass";
min[2]=55;
max[2]=255;
filter[2]="pass";
for (i=0;i<3;i++){
  selectWindow(""+i);
  setThreshold(min[i], max[i]);
  run("Convert to Mask");
  if (filter[i]=="stop")  run("Invert");
}
imageCalculator("AND create", "0","1");
imageCalculator("AND create", "Result of 0","2");
for (i=0;i<3;i++){
  selectWindow(""+i);
  close();
}
selectWindow("Result of 0");
close();
selectWindow("Result of Result of 0");
rename(a);
// Colour Thresholding-------------


run("Analyze Particles...", "size=0-Infinity display exclude add");

// use the results table to calculate total area of object and then [brightness area] / [total area]
selectWindow("Results"); 
totalArea = getResult("Area", 0);
brightnessArea = 0;
for (i = 1; i < nResults(); i++) { // want to skip the first big ROI
	brightnessArea = brightnessArea + getResult("Area", i);
}
print("total Area = " + totalArea);
print("brightness Area = " + brightnessArea);
print("ratio = " + brightnessArea/totalArea);

I changed this code - so now it’s taking into account brightness instead. Before - since they were RGB images - I was just thresholding for ‘blue’ pixels. You should modify this code to fit your needs though - eh?! It might not be the final product - but it gives you a start.

And the areas measured look like this:

and you get printed results like this:

(you can also see my upcoming concert tickets in the top left of the screen!)

If you are using the most updated version of Fiji (which we recommend for new users) - just go to File > New > Script and this will open the Script Editor. Select the ImageJ1 Macro language, have ONE of your images open (only one for now based on the code I’m providing you), and click Run. :slight_smile:

eta


#5

Thanks! @etarena - I really appreciate your help!:grinning:
I’ve already tried this code on a few additional images today, and it looks like it works pretty well so far!

Since I am a novice when it comes to reading code, could you confirm if my general interpretation is correct? If not, could you correct my understanding? I’d like to be able to explain how this macro works to say that it decreases/eliminates subjective assessment. (see below)

My understanding is that the macro is essentially (with one image at a time):

  • Converting the image to 8bit grayscale (to focus on brightness)
  • Duplicating the image (One to measure the total area, and one to measure the area of interest)
  • Thresholding the original image to measure the area of contamination.
  • Providing a calculation of the contamination area over the total area.

Lastly, I am curious about 3 items in the code.

  1. What exactly is “mean dark”? I’ve tried AutoThresholding before, but I don’t have “mean dark” threshold option.
  2. I noticed that in both codes you provided a brightness threshold of 40 (in the first) and 55 (in the updated). Could you explain how was the minimum brightness threshold of 55 was determined?
  3. The code outputs a image titled “mask”. Is this the total area image, and is the measured contamination image being subtracted from it to get the ratio?

Thanks,
Adam


#6

I’m glad it’s helping @adamczibur:slight_smile: Again - it’s not a finished product… so I would advise you to work a little bit on Scripting in ImageJ. And use those links I provided you above! :slight_smile: It’s worth your time in the end… you can even make this script run on multiple images in folders/subfolders if you want it even more automated! :slight_smile:

Too - you are going to want to brush up a little on Image Processing and Segmentation, etc. Here are a few other helpful links:

So… for your specific questions:

What you wrote is fine! :slight_smile: So… I got that code from Opening the Macro Recorder, and then running those functions in order… if you click Create from the Macro Recorder… it moves that code over to the Script Editor - and there you have the makings of an initial script! :slight_smile: (All of that is gone over in the Scripting workshop I linked above.)

So it’s using the Mean auto threshold algorithm - and then dark part just indicates that I had checked the Dark background button on the threshold dialog-thingy… that’s all. :slight_smile: If you play around with the recorder - you’ll see how the code syntax changes with those options, etc.

This is where things got subjective. I kind of arbitrarily picked that cut-off. Which is not ideal - no. As far as I know - there is no auto-threshold method for using the Color Threshold… which makes me think there is an even better way for you to run this code. If you just care about ‘brightness’… then I would just convert to 8-bit as we did before… but this time - use a different Auto Threshold method for the ‘bright’ signal you are interested in. I’ll play a bit and repost another code example that will do it this way.

The mask image is the one that covers the ‘total area’ of your object - not the entire image. Though - perhaps you want to change that? It’s up to you. I didn’t subtract anything - just calculated the ratio of bright signal area / total object area. That’s what I had interpreted from your posts above… but I could have been wrong.

Again - you can change this code any way you want to - just play around a bit. I don’t know your science in particular - you obviously know better… so adapt it to fit your needs.

eta :slight_smile:


#7

Here is an updated version that doesn’t use the Color Threshold… instead, I used Huang auto threshold method to grab those ‘bright’ pixels within the object (ie - way more objective):

// have the original image opened
run("8-bit"); // convert original image to 8-bit
originalImage = getImageID(); // save image ID
run("Duplicate...", " "); // duplicate the image

// now working on the duplicate
setAutoThreshold("Mean dark"); // grab the whole object area
//run("Threshold...");
run("Create Mask"); 
run("Analyze Particles...", "size=100-Infinity display exclude add");

// now move back to the original image (which is 8-bit now!)
selectImage(originalImage);
roiManager("Select", 0); // overlay ROI of the whole object area on original Image
setAutoThreshold("Huang dark"); // use a different auto threshold method to grab the bright pixels
run("Analyze Particles...", "size=0-Infinity display exclude add"); // create ROIs of those bright pixels and measure

// use the results table to calculate total area of object and then [brightness area] / [total area]
selectWindow("Results"); 
totalArea = getResult("Area", 0); // this is the total area of the whole object
brightnessArea = 0;
for (i = 1; i < nResults(); i++) { // want to skip the first big ROI
	brightnessArea = brightnessArea + getResult("Area", i); // adding up all the other ROIs (so all the bright pixel areas together)
}
print("total Area = " + totalArea);
print("brightness Area = " + brightnessArea);
print("ratio = " + brightnessArea/totalArea);

As always - if you have more questions, etc. - we are all here to help!

eta :slight_smile:

Note - you can update this code above - so instead of printing out the results - you can save them in a table form… just use those links I provided you about Scripting in ImageJ… too - you can apply this to multiple images in folders/subfolders, etc. but I will leave that to you


#8

Hi @etarena,

Sorry for my delay response. I was on vacation and travel for the past few weeks.

I’ve used this macro on a few images, and it looks like I am getting some weird results from the Auto Thresholding (e.g. percentages of a fully contaminated surface measuring about 6% contaminated). Is there anything you see in this macro that could be causing these misreads?

Thanks,
Adam


#9

@adamczibur

I have no idea. You might need to try a different Auto-Threshold method… or tweek the script I gave you to get the results you deem appropriate?? I don’t know the science behind what you are doing… so I can only provide a framework. It’s best if you can modify/adapt the macro as you see fit.

If you want - you can share here the specific images that are not ‘working’ as you’d like - so we can try to recreate the issues. But describe what you think is wrong, etc. Too - these automated methods are not always 100% correct on all images… there is some error that you will have to live with, but for sure - we can try to minimize it as best as possible.

Before anything though - take the time to go through the links I provided you above - you might be able to then fix this issue you are having yourself too - which is ideal! :slight_smile:

eta