Macro to ID red drupelet reversion

Hello!
I am attempting to write a two-part macro and am having difficulties getting it to transition from part one to part two. I’m sure this is quite simple to those of you who have computer skills and i would greatly appreciate your instruction.

Part 1: Set color threshold HSB parameters to find total area of blackberry fruit and Analyze Particles!
Part 2: Adjust parameters to find area of drupelets that are red to purple and Analyze Particles!

In this latest code, the original image is restored between part 1 and part 2. The error message that usually comes up is the Converter—supported conversions window.
All in all, looking at a picture of blackberries, i want to find the ratio of red drupelets to total drupelets.
Any help or suggestions are welcome!
Thanks,
Bethany

Below is one of my many attempts:

run("Color Threshold...");
// Color Thresholder 1.50i
// 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]=5;
max[1]=255;
filter[1]="pass";
min[2]=0;
max[2]=205;
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...", "include summarize");
run("RGB Color");
// Color Thresholder 1.50i
// 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]=5;
max[1]=255;
filter[1]="pass";
min[2]=0;
max[2]=205;
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-------------
// Color Thresholder 1.50i
// 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]=5;
max[0]=235;
filter[0]="stop";
min[1]=5;
max[1]=255;
filter[1]="pass";
min[2]=0;
max[2]=205;
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...", "include summarize");
run("RGB Color");

Welcome to the forum, @bsebesta!

I have several thoughts (not necessarily in order of importance):

  1. It would help if you could post a sample image. Then people here on the forum can help you craft a workflow which works on your specific data.

  2. Your macro would benefit from using a function for the color thresholding steps. As it stands, there is a lot of repeated cut-and-paste code. Here is a modified version of your macro which is substantially shorter:

    function colorThreshold(filter0, filter1, filter2) {
      // Color Thresholder 1.50i
      // 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]=filter0;
      min[1]=5;
      max[1]=255;
      filter[1]=filter1;
      min[2]=0;
      max[2]=205;
      filter[2]=filter2;
      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("Color Threshold...");
    colorThreshold("pass", "pass", "pass");
    run("Analyze Particles...", "include summarize");
    run("RGB Color");
    colorThreshold("pass", "pass", "pass");
    colorThreshold("stop", "pass", "pass");
    run("Analyze Particles...", "include summarize");
    run("RGB Color");
    
  3. Your immediate error comes from the statement run("HSB Stack"); during the third color thresholding. Now that we have a function, it is apparent that in between the first and second color thresholding operations, you use run("RGB Color"); to go back to RGB type, but in between the second and third color thresholding operations, you do not—so the third color threshold operation tries to execute on 8-bit grayscale data, which does not work. (You can verify this yourself by opening an 8-bit grayscale image such as File :arrow_forward: Open Samples :arrow_forward: Bridge and then Image :arrow_forward: Type :arrow_forward: HSB Stack and you’ll get that same error.)

  4. You said you want the ratio of red objects to all objects. You should only need to use a single Color Threshold operation to achieve this, no? (Or maybe even sneak by simply using the red channel alone? Depends on the data.) One idea which occurs to me is:

    • Do a normal grayscale thresholding to achieve a segmentation of all objects (or color thresholding for that, but only if grayscale thresholding and TWS are not good enough).
    • Use the segmentation to measure histograms on your original RGB image.
    • Do some math on the histogram to get the ratio.

    But other more expert image analysts here may have better ideas.

Again: post a sample image and people will be more equipped to help!

1 Like

Hi ctrueden,

Thank you so much for your ideas and suggestions!

Absolutely, please find picture attached:

As you can see in the picture, the reverted drupelets are not necessarily red only. If the drupelet is no longer black, it is considered “reverted” and this can range from dark purple to bright red.

I will attempt the code you have provided and other suggestions and give you an update on my progress soon.

Bethany

Some things i should clarify:

-the purpose of the first process is to calculate total area but to exclude the spaces where it is so dark you can not see anything; therefore, the total area will not be 100%.
-the second process is able to pick up “reverted” colors well at those color threshold settings.

Thanks!

Thanks for posting the picture, @bsebesta!

Edit: I think I found a workflow which works better. Stay tuned; I will post it below.

That data is very challenging to analyze, for several reasons. For example, the light source illumination is highly uneven. I tried doing a background subtraction followed by the TWS plugin, but it is tough.

Without background correction

Here you can see how the left-side light source causes the segmentation to favor that side.

After background correction

To correct background, you can duplicate the image, then apply a large-radius (maybe 50 pixels) Gaussian blur to the duplicate, then subtract it from the original using the Image Calculator. But as you can see, the result is still pretty bad.

After background correction + morphological smoothing + size filter

You can smooth the shapes by using the Close- (not Close!) and Fill Holes commands. You can filter by particle size using Analyze Particles. This helps, but the quality of the input segmentation was too poor here, so we are left with many reverted berries not detected.

There are others here on the forum who are capable of doing a better job than this, and who probably have suggestions on how to improve your acquisition process to generate easier-to-segment images.

If you have not read it already, please check out the Principles page of the ImageJ web site, which covers both how to acquire images in a better way, as well as how to analyze them, pitfalls to avoid, etc.

OK, here is a workflow which does not use TWS, nor the Color Threshold plugin, but produces relatively good (to my non-expert eye) results. First, the result itself:

Secondly, the macro which produced it:

// @ImagePlus image

// Clear the ROI Manager.
//roiManager("Deselect");
//roiManager("Delete");

// Stash a copy of the image for later.
run("Duplicate...", "title=gray");

// Make an 8-bit red image.
run("Duplicate...", "title=rgb");
run("Split Channels");
close(); // close the blue channel
close(); // close the green channel
rename("red");

// Make an 8-bit grayscale image.
selectWindow("gray");
run("8-bit");

// Make an 8-bit "red difference" image where intensity = red - gray.
imageCalculator("Subtract create", "red","gray");

// Threshold the red difference image.
selectWindow("Result of red");
setAutoThreshold("Otsu dark");

// Smooth the shapes a little, and cut them into pieces with Watershed.
run("Create Mask");
run("Watershed");
setOption("BlackBackground", true);
run("Erode");
run("Dilate");

// Filter by size, putting 100+ sized particles into the ROI Manager.
run("Analyze Particles...", "size=100-Infinity include add");

// Close intermediate images.
selectWindow("mask");
close();
selectWindow("Result of red");
close();
selectWindow("gray");
close();
selectWindow("red");
close();

selectImage(image);
roiManager("Show All");

Caveats

  • I realize that this is only half of your equation: it segments the red berries, but not the black berries. It may be easiest to segment the background specifically, since they are large dark areas.
  • This workflow is not performing a background subtraction as discussed above; you probably still want to do that, since I expect it will further improve the segmentation.
  • The image you posted is a JPEG, which are known to be problematic for quantitative image analysis. If possible, tell your camera to record the images using a lossless format like TIFF, rather than a lossy one like JPEG.

Definitely follow up if you need further assistance!