Distinguishing between two types of soil

Hi all,
I’m new in processing image. I work on soil samples to identify its composition byusing imagej. It’s a mixture of two different materials, silica and silt. These two materials have a different attenuation coefficient. I tried to use different types of thresholding to distinguish between the two materials, but it didn’t give any results.
So I am looking for a method that I can use to differentiate between my two types of soils Image soil.tif (1.6 MB) ??
Thanks in advance

ps: each stack is formed by 900 slices.

Could you share an annotated version of the image so we can see how the two soils differ in appearance? The slice you provided appears to have have low contrast and the gray-scale distribution appears to be uni-modal. It will be nearly (if not entirely) impossible to separate them using a threshold if the distribution is uni-modal.

Hi Andrew,
That’s usually the problem. It is complicated to distinguish between the two soils by thresholding because their attenuation coefficients are relatively close. However, The sand grain size distribution range between 160 and 400 µm. And de silt is less than 100 µm.
This is another slice with the legend. The black spaces represent the void. The dark grey shows the silt and the light grey ( near white) is the sand.
The second image below represents one type of soil: sand (without silt).

If the different types of soil have similar attenuation coefficients but distinguishable grain size ranges, then perhaps you could use thresholding to separate them from voids, run Local Thickness to get estimate the grain sizes, and then segment based on that. Note that, depending on the size of your image, Local Thickness may take a while. I have used it on stacks that are about 2000x2000x200 voxels and it can take as much as a few hours.

Can you tell me please how I can use Local thikness to estimate particles size?

Local Thickness takes a binary (8-bit gray-scale consisting of 0 and 255) image stack and uses sphere fitting to get a diameter (in voxels and assuming isotropic voxel dimensions) for each voxel in the target phase (either 0 or 255). The plugin produces an image stack where the value of each voxel is the diameter that was calculated using sphere fitting. In case you are unaware, voxel is a term sometimes used to refer to pixels in image stacks. It is meant to emphasis that there are more than 2 dimensions associated with a given pixel.

Since it is difficult to distinguish between the soil types based on attenuation, I am thinking you could just have Local Thickness calculate the diameters for all soil pixels and then segment the resulting image based on diameter. The value assigned to a pixel by Local thickness is the diameter of the largest sphere that fits within the target phase and contains the specified pixel within its volume. Therefore, if you were to run it on a perfect sphere, all of the pixel that make that sphere should be given the same value.

1 Like

Hi folks,

Perhaps WEKA segmentation could also be something to include in consideration?

Video on using WEKA on 3d-stack to have an idea what it is / can do:

1 Like

Wouldn’t hurt to try but from my own experience I would expect much given a dataset like this.


Hello Userfiji,
IF you haven’t already found your solution, then you may want to apply the k-means cluster plugin. It works quite well for this type of image.

1 Like

Hi Smith,
I’m testing wek segmentation. If it doesn’t work, I’ll try to work with k-means cluster plugin.
Thank you

Hi Danielle,
I’m testing wek segmentation. I hope to get good results.

Thanks Anderew for your explanations

I tried to use WEKA segmentation and got the following results. So I still have to calculate the percentage of each fraction.
I tried to use thresholding but it doesn’t work, because it separates the pixels just in two phases, black (void) and white (other phases).
Do you have any idea how I can output the fraction of each phase?
Thanks in advance


I applied the RGB -> CMYK plugin to your yellow image.
I have results on the thresholded channel Y with Li and also with Mean. A test can be interesting for you.

I applied also the RGB -> CMYK plugin to my image.
And I got the results on the thresholded channel Y with Li.
But my question is, how can I get out the fraction (%) of each phase (3 phases)?
i.e. How can I get the particles areas from the histogram? And what means “start bin” and “width bin”?


I made this macro.
Open an image (the yellow).
Copy the macro to "Plugin-New-Macro)
Then run.

It will obviously be necessary to check:

  • thresholds
  • the results

Please, I would like to have a report if you don’t mind.

requires( "1.52t" );
setBackgroundColor(0, 0, 0);
setOption("BlackBackground", true);

run("Set Measurements...", "area bounding limit display add redirect=None decimal=2");
doWand(40, 628,0,"Legacy");
run("Fit Circle");
run("Enlarge...", "enlarge=-25");
setBackgroundColor(255, 255, 255);
run("Clear Outside");
run("Select None");

// Duplicate the section
run("Duplicate...", "title=1");
run("Duplicate...", "title=2");

// Measure Area of your section
waitForUser("Measure Section Area,\nIt's Circle red?\nIt's OK?");
run("Convert to Mask");
Asection=getResult("Area", 0);
print("Asection="+Asection) ;

// Measure Area of the crown of your section
run("Create Selection");
Acrown= getResult("Area", 1);
print("Acrown="+Acrown) ;
waitForUser("Measure Crown Area,\nIt'scrown select?\nIt's OK?");

// Make original image in stack CMYK
run("RGB to CMYK");
run("Stack to Images");

// All that is green on your picture
roiManager("Select", 0);
setBackgroundColor(255, 255, 255);
run("Clear Outside");
setAutoThreshold("Default dark");

waitForUser("Compare image 2 with C,\nIt's all green in original image select?\nIt's OK?");
setOption("BlackBackground", true);
run("Convert to Mask");
roiManager("Select", 0);
Agreen= getResult("Area", 2);
print("Agreen="+Agreen) ;

// All that is red on your picture
roiManager("Select", 0);
setBackgroundColor(255, 255, 255);
run("Clear Outside");
setAutoThreshold("Yen dark");

waitForUser("Compare image image 2 with M,\nIt's all red in original image select?\nIt's OK?");
setOption("BlackBackground", true);
run("Convert to Mask");
roiManager("Select", 0);

Ared= getResult("Area", 3);
print("Ared="+Ared) ;

Apurple= Asection -(Ared +Agreen);
print("Apurple="+Apurple) ;

run("Close All");
close("ROI Manager");
exit("It's Over");
exit("The results is wrong");}


1 Like

Hi @Mathew,
Thank you for your help.
The results obtained by this script give wrong results.
As you can see on the screen below, the surface of the disc = 799624 pixels
I’ve covered this surface area in mm2, it gives 422380 mm2 (with a resolution of 18.86px/mm).
So, the real area of my sample (disc) is about 2290 mm2.
there’s a problem somewhere, I don’t know where exactly…


I don’t understand why you want to convert the area to metric units?
Your question was:

There seems to be a problem with the macro results:
Can you give me the scale of your “yellow” image?
Your image is 1300 x 1279 px. What are the dimensions in metric units of your image?

I arrive at a sum of thresholded areas equal to the area of ​​the disc +/- 6%(in px). I do not know if this is acceptable for your work?

1 Like

Hi @Mathew
In the present work, I must present my results in metric units, in order to compare image processing results with the experimental analyses carried out on the same samples in the laboratory.

18.86 px/mm
Disc area = 2290 mm2