Threshold macro issues bioassay

Hello,

I am kind of new to ImageJ and would like to write a macro for measuring lesion size of a fungus growing on a leaf disc.

The macro would be useful when I increase the number of images I want to analyse. Right now I can do it manually, by selecting with freehand selection and cut out all useless items in my image (which disturb my manual thresholding) and then adjust the threshold so that only the lesions are colored red. This works perfect, but it is sort of labor intensive.

The disturbands are things such as labels to identify the genotype of the leaf disc and the border of the container I have the bioassay in, which is translucent, but gives light reflection. (which both are hard to remove from my original setup.

My idea is to first select the leaf discs using a certain threshold then clear the outside, (I found thresholds which allow me to select only the discs) and then use clean leaf discs for lesion determination. Which again works perfectly manually.

When I try to macro the color thresholding I end up with a inverted masked 8-bit picture, which does not really come in handy.

Does anyone have tips?

Thanks a lot!

This is what I have done so far:

open("img_path");
run("Set Scale...", "distance=100 known=5 pixel=1 unit=mm global");
run("Smooth"); // x20 (since I have a lot of reflection from water on the leaf discs)

// Color Thresholder 1.52a
// 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]=75;
filter[0]="pass";
min[1]=75;
max[1]=255;
filter[1]="pass";
min[2]=1;
max[2]=200;
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("Create Selection");
setBackgroundColor(0, 0, 0);
run("Clear Outside");

//This is where I should have only the leaves left, so I can select the lesions with threshold, without contaminations of other lights.

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]=46;
filter[0]="pass";
min[1]=0;
max[1]=255;
filter[1]="pass";
min[2]=1;
max[2]=200;
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=5-Infinity display clear include summarize add");

Good day,

just some remarks:

For scientific work, never ever work with manually set thresholds.
Use a suitable automatic threshold scheme and stick to this scheme for similar kinds of images.

Never use JPG-compressed images for scientific purposes.
Use images in either RAW, TIF- or PNG-format.
For this Forum, please post typical images in the original TIF- or PNG-format. No JPG-format though, because JPG introduces artifacts!
(Converting a JPG-compressed image to TIFF- or PNG-format doesn’t make sense.)
You may also post images as Zip-archives or make them accessible via a dropbox-like service.

For your task it may be useful to have a look at the image after RGB to CMYK conversion (here you can download the plugin). The Magenta color channel looks promising for segmentation within the leaf areas. (Finding the leaf areas is rather easy.)

Please learn how to code ImageJ-macros:
https://imagej.nih.gov/ij/developer/macro/macros.html
https://imagej.nih.gov/ij/developer/macro/functions.html

Regards

Herbie

Dear Herbie,

Thanks for your reply.

Sadly and I know it is terrible, I should not have been using JPEG. But my test setup (experimental) only allowed me to create JPEG images, as it is the standard and only format my camera makes. When everything works and I will upscale my test, different camera will be used.

I will try the RGB to CMYK conversion.

Here is a first result of automatic leaf segmentation:

More remarks:

  • Illumination is uneven: Darker on the left. This should be improved.

  • Two leafs in the upper left quadrant are nearly touching. This should be avoided to make segmentation easier and more robust.

  • Use a stable tripod or repro-stand for the camera and ensure that the position of the camera relative to the tray is constant.

These are such obvious facts and easy to remedy!

The best image processing is no image processing, which means optimum sample preparation and image acquisition. Any post hoc processing is costly and can never fully compensate for sub-optimum preparation and acquisition!

Regards

Herbie

Dear @anon96376101,

When I use the CMYK conversion I do get nice segmentation within the leaf areas (Magenta channel), but when I tro to threshold and make binary I still get the outer edges and labels also within my threshold.

The best result is when I use triangle.

I don’t fully understand what you are doing.

How comes you have stacks?
If you have stacks you need of course tick “… for each image”!

For a single image I’d use a two-step process:

  1. segment the leafs and store the 24 ROIs in the ROI-manager
  2. loop over the leafs by using the ROI-manager
  3. threshold the individual leafs e.g. after RGB>CMYK-conversion
  4. get the areas

I suppose that you are interested in the area sizes that are brown (fungus) or in the relation of this size to the whole leaf area.

Please study the ImageJ User Guide and try to develop a robust processing scheme.

Regards

Herbie

Dear Herbie,

You make some valid points, these should definitely be improved. This is then ofcourse still all really experimental.

When I used manual thresholding I received very nice selections, which I could then use for analyze particles PNG

Please read carefiully:

No manually set thresholds, because they don’t generalize and they pose problems with reproducibility.

Manually set thresholds are a no-no for scientific work!

Herbie

Hi Herbie,

Yes hence why I came here, haha :wink: This is from my earlier work.
I got the stacks when I converted my RGB image to CMYK.

How did you make the automated leaf segmentation?

Kind regards,
Paul

I got the stacks when I converted my RGB image to CMYK.

OK.
(I got puzzled by the previous image that shows a lot of trays in one image, but I now understand that this should serve to show the problem with thresholds.)

I’d not use the RGB>CMYK-conversion for leaf segmentation.

How did you make the automated leaf segmentation?

Please spend a minimum of, say three hours to try to achieve this result starting from the sample image converted to 32bit gray. If you don’t get a similar and reasonable result I shall give you more hints.

Good luck

Herbie

Hi Herbie,

Haha, I’m sorry for my ignorance :wink:

Thanks, I’ll try!

Kind regards,
Paul

Hello Paul,

based on your original macro, I added some parts to loop over the selected leaf-discs and use the color threshold on each of them. The result will have the leaf-discs in the overlay and the damage-zones in the roi-manager.

Best regards,
Volker

originalImageID = getImageID();
run("Duplicate...", " ");
run("Set Scale...", "distance=100 known=5 pixel=1 unit=mm global");
run("Smooth"); // x20 (since I have a lot of reflection from water on the leaf discs)

// Color Thresholder 1.52a
// 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]=75;
filter[0]="pass";
min[1]=75;
max[1]=255;
filter[1]="pass";
min[2]=1;
max[2]=200;
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);

run("Fill Holes");
run("Analyze Particles...", "size=100-Infinity show=Overlay");
Overlay.copy;
close();
Overlay.paste;
// Colour Thresholding-------------
roiManager("reset");
//This is where I should have only the leaves left, so I can select the lesions with threshold, without contaminations of other lights.
count = Overlay.size;
for (i=0; i<count; i++) {
	 Overlay.activateSelection(i);
	 getBoundingRect(x, y, width, height);
	 run("Duplicate...", " ");
	 colorThreshold();
	 run("Create Selection");
	 getBoundingRect(x2, y2, width, height);
	 hasRoi = (selectionType()>-1);
	 if (hasRoi) roiManager("Add");
	 close();
	 if (hasRoi) {
	 	roiIndex = roiManager("count")-1;
	 	roiManager("select", roiIndex);
	 	Roi.move(x+x2, y+y2);
	 	roiManager("Update");
	 }
}
run("Select None");


function colorThreshold() {
	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]=46;
	filter[0]="pass";
	min[1]=0;
	max[1]=255;
	filter[1]="pass";
	min[2]=1;
	max[2]=200;
	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");
}
1 Like

Here is an ImageJ-macro that works well for the provided sample image and it is expected to generalize reasonably to similar images:

Minor Update

// imagej-macro "lesionArea" (Herbie G., 22. Nov. 2018)
requires( "1.52h" );
run("Clear Results");
run("Set Measurements...", "area redirect=None decimal=0");
run("Set Scale...", "distance=0");
setBackgroundColor(0, 0, 0);
setOption("BlackBackground", true);
setBatchMode(true);
run("Subtract Background...", "rolling=500 light");
run("RGB to CMYK");
run("Stack to Images");
close("K"); close("C");
setAutoThreshold("Otsu dark no-reset");
run("Convert to Mask");
run("Close-");
run("Fill Holes");
run("Analyze Particles...", "size=10000-Infinity circularity=0.50-1.00 show=Nothing exclude add");
n=roiManager("count");
if (n!=24) exit("Found "+n+" ≠ 24 leafs.");
roiManager("multi-measure measure_all append");
Table.renameColumn("Area", "Leaf Area");
close();
roiManager("Show All");
run("Set Measurements...", "area redirect=None decimal=3");
for ( i=0; i<n; i++ ) { 
   roiManager("select", i);
   run("Duplicate...", "title=temp");
   run("Clear Outside");
   setAutoThreshold("Triangle dark no-reset");
   run("Convert to Mask");
   run("Fill Holes");
   List.setMeasurements;
   val=round(List.getValue("Mean")*List.getValue("Area")/255);
   setResult("Lesion Area", i, val);
   val/=getResult("Leaf Area", i);
   setResult("Fraction", i, val);
   close();
}
close("M");
run("From ROI Manager");
setBatchMode(false);
exit();
// imagej-macro "lesionArea" (Herbie G., 22. Nov. 2018)

Here is the “Results”-table I get for the provided sample image:
screenShot

“Leaf Area”- and “Lesion Area”-values are in Pixel^2.

The macro applies two different automatic gray-threshold schemes (no manually set thresholds and no fixed color thresholds).
With the “Otsu”-scheme, the leafs are segmented from the “Y”-channel and with the “Triangle”-scheme, the brown areas are determined from the “M”-channel excerpts.

If you use this macro for your work and if this work will find its way to any kind of dissertation, report or publication, then you must mention that you’ve received help.

Regards

Herbie

1 Like

Hi volker,

I used this macro and worked quite well with my images.
Only problem is I did not get any displayed results.
Can I get results in two column, one for total area and one for damage area for same image file?

Thanks
Babu

Hi Babu,
first some remarks:

  1. The method used is from @Paultjeftw, I only wanted to use to independent sets of rois, one in the roi-manager and one in the overlay.

  2. I agree with @anon96376101 that using fixed threshold values is not a good idea, his solution is better.

  3. The macro sets the spatial scale in the beginning which you need to adapt to your images. It would not work with pixels without changing the size in the analyze particles command.

  4. The two roi-sets do not work out so nicely because there is a problem when there leaves without lesions. I add the roi of the whole leaf in this case to have the labels consistent.

So just to show how you could measure the two roi-sets:

originalImageID = getImageID();
run("Duplicate...", " ");
run("Set Scale...", "distance=100 known=5 pixel=1 unit=mm global");
run("Smooth"); // x20 (since I have a lot of reflection from water on the leaf discs)

// Color Thresholder 1.52a
// 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]=75;
filter[0]="pass";
min[1]=75;
max[1]=255;
filter[1]="pass";
min[2]=1;
max[2]=200;
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);

run("Fill Holes");
run("Analyze Particles...", "size=100-Infinity show=Overlay");
Overlay.copy;
close();
Overlay.paste;
// Colour Thresholding-------------
roiManager("reset");
//This is where I should have only the leaves left, so I can select the lesions with threshold, without contaminations of other lights.
count = Overlay.size;
totalAreas = newArray();
leasionAreas = newArray();
for (i=0; i<count; i++) {
	 Overlay.activateSelection(i);
	 getStatistics(area);
	 totalAreas = Array.concat(totalAreas, area);
	 getBoundingRect(x, y, width, height);
	 run("Duplicate...", " ");
	 colorThreshold();
	 run("Create Selection");
	 getBoundingRect(x2, y2, width, height);
	 hasRoi = (selectionType()>-1);
	 area = 0;
	 if (hasRoi) {
	 	roiManager("Add");
	 	getStatistics(area);
	 }
	 leasionAreas = Array.concat(leasionAreas, area);
	 close();
	 if (hasRoi) {
	 	roiIndex = roiManager("count")-1;
	 	roiManager("select", roiIndex);
	 	Roi.move(x+x2, y+y2);
	 	roiManager("Update");
	 }  else {
	 	Overlay.activateSelection(i);
	 	roiManager("Add");
	 }
}
run("Select None");

title = getTitle();
reportResults(title, totalAreas, leasionAreas);
roiManager("Show None");
roiManager("Show All without labels");
run("Labels...", "color=white font=9 show draw");

function reportResults(imageTitle, totalAreas, leasionAreas) {
  title = "Leasion Measurements";
  handle = "["+title+"]";
  if (!isOpen(title)) {
     run("Table...", "name="+handle+" width=800 height=600");
  	 print(handle, "\\Headings:n\timage\ttotal area\tleasion area\tleasion fraction");
  }
  for (i=0; i<totalAreas.length; i++) {
     print(handle, (i+1) + "\t" + imageTitle + "\t" + totalAreas[i] + "\t" + leasionAreas[i]+"\t"+leasionAreas[i]/totalAreas[i]);
  }
}

function colorThreshold() {
	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]=46;
	filter[0]="pass";
	min[1]=0;
	max[1]=255;
	filter[1]="pass";
	min[2]=1;
	max[2]=200;
	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");
}

Best regards,
Volker

@anon96376101 @volker

Thanks a lot for your input, I am still trying yours macro’s and adjusting them to my needs. I appreciate your help A lot.

Sorry for not keeping you updated, but real busy and this sadly does not have my priority. I am not even sure if this bioassay gives the results I want.

If I end up using this I ofcourse will citate you and or will update you! :slight_smile:

Thanks.


Hello, could you help me with this image? I’ve tried many things, but I can’t get the data like the example shown here.
Porvafor.