How to Create a Distribution of Diameter of Particles in a TIFF stack as Diameter varies between each TIFF file

Hello. I have been trying to find a way to analyze thousands of particles suspended in a CT scan using Fiji and a TIFF stack of 700 or more images and place the diameters of each particle in a histogram so I can see what this distribution looks like. I was unsure the best way to perform this analysis as each particle shows up on successive images within the TIFF stack and is counted multiple times each with a different diameter, thus skewing the results. I am modelling this as spheres in another program like Blender and spheres have perfect symmetry so every diameter is counted twice (one per hemisphere) and I am unsure of how to account for this as well. Thank you so much for any input you can give!

What is your exact question? How to locate particles, how to count particles, how to find which diameter to use, which slices belong together, how to plot?
What have you tried and achieved so far and where do you get stuck?

In general, did you read some stereology articles? If not, you want to look into literature on the stereology subject DISECTOR (spelled with one S) in particular, an article written by Sterio (a pseudonym). This explains on how to select the particle when counting. But then, you might be familiar with the field already.

I’m mainly concerned with how many particles in the TIFF stack share a certain diameter so that I can use other modelling software to generate a simulation involving said particles using a particle size distribution centered around the average of the diameters of all the particles in the TIFF stack. I have no clue how to account for the repeated counting of the same particle as it grows and shrinks within the TIFF stack…

This definitely is the disector case.
Then you want to create a logical combinatory projection between slices to find out where a new sphere starts. This boils down to finding the center of each disk in slice 1 and checking if at (x,y) in the next slice the pixel at (x,y) is empty or filled. If empty, your particle is gone (and has a diameter of what you measured or at maximum the slice distance). Chance is that you started scanning beyond the center of the particle: bad luck, consider it a ‘particle touching the edge’ and ignore it.

If not empty at (x,y), measure in the next slice the diameter of the disk with center (x,y). The magic wand does wonders here.
As long as the diameter increases, rinse and repeat. If the diameter of particle at (x,y) decreases wrt. the previous slice, that was your maximum diameter; store and mark (x,y) as ‘dealt with’ until there is no longer a particle at (x,y); rinse and repeat.

Your spheres must have a diameter of at least the distance of two C slices apart (which you did not specify) or they can be missed.

You’ll end up with an array of x and y’s that are within the respective spheres, and the maximum diameter you measured as particles.
You could elaborate and also measure how many z-slices contain the particle. Chances are that you find some Z-distances to be larger than that particle’s diameter.

I cannot fathom your image processing skills, so try to make your subsequent question, if any, as specific as possible and, once more, please tell what you already have done, and where you got stuck.

I just started using ImageJ and Fiji about a month ago. I’m guessing I have to write some Python to check if a particle was counted and loop it for the length of the tiff stack? Thank you so much for pointing me in a new direction

I know I am late to the party here but perhaps 3D Objects Counter would help?

1 Like

I used 3D Object Counter and to no avail since the file size is so massive even in binary. I tried using smaller sample sizes and couldn’t get an adequate picture of what is going on. Stereology seems to be the most potent method of approximating each volume of the particle and I can use just a few tiff files to get what I need.

1 Like

You can (using ImageJ1 and later moving to FIJI) record your steps using Plugins>Macro>Record and then use the recording as core of a Batch process or wrap it in a few lines of code that walk your files in each folder.

There are a few ways you can approach the analysis. One uses binary calculations, the other one uses ROIs. I assume you have binary images that contain 0 in the pixels where no particle is present, and 255 in pixels with particles. If particles are sparsely distributed, the strategy below is simple, if the volume is densely populated, you might need to add a few checks for multiple particles in one slice being present in the same projected location of a particle in the previous slice, but this is dealt with using stereology.

You can make a list of all centers in the entire stack first or you can iteratively use two images at a time. The latter allows for arbitrary large data sets, as long as two images fit in memory (with all the temporary images, that is).

Once you have chosen your strategy, you open an image, analyse the particles yielding center and radius for each particle. Here two options are possible: you either do some image calculation between consecutive images (image n AND image n+1) to see where they overlap; check each center you found in image n against presence or absence in that location of an object in the ANDed image. Rinse and repeat.
Or you use the ROI manager to store all particles in image n, then for each ROI measure the area in image n+1 and keep track of an increase or decrease of radius. Rinse and repeat.

A simple method for finding the radius of a disk in slice n+1 given the center (x,y) of a disk in slice n is to use the command “doWand(x,y)”. If the measurement of the particle yields a center location that is not in the particle of the previous slice (or doesn’t find a particle), image n+1 no longer contains that particle. If the radius measured is bigger, you are not in the center yet, if the radius is smaller, you are past the center.

All steps you try will be in the Recorder window, ready to copy and paste into a Plugins>New>Macro. In ImageJ1, when editing Macro text, a Debugger is available to let you step through your code and inspect variables. How is your general programming knowledge?

My programming knowledge is few and far between. I understand Python script but have no idea how to use it in the application (I’m guessing using the macro box?). Do I just input the x and y pixel coordinates for each center? There is about 30 to 40 distinguishable particles each layer with some being really small and too numerous to distinguish from the background noise.

Unfortunately, I don’t speak Python. In general, in a macro, you choose your language and call built-in functions. As ImageJ and FIJI are recordable, that is a great help. There is a built-in function list in ImageJ1 and an overview of the macro language. If you can read but not write Python, you might as well start with ImageJ(1) scripting and once you have familiarised yourself with the principles of image processing, move on to other languages.

Here is a bit of sample code; the code creates a three-slice stack, draws some disks representing particles and tests if found objects are present in the next slice. Working with separate images rather than a stack requires a bit more bookkeeping.

run("Close All");
	run("ROI Manager...");roiManager("Deselect");roiManager("Delete");
newImage("threeslice", "8-bit black", 512, 512, 3);
makeOval(78, 65, 114, 114);setForegroundColor(255,255,255);run("Fill", "slice");run("Next Slice [>]");makeOval(72, 282, 106, 106);run("Fill", "slice");run("Next Slice [>]");makeOval(56, 266, 122, 122);run("Fill", "slice");makeOval(337, 199, 71, 71);run("Fill", "slice");run("Previous Slice [<]");makeOval(326, 188, 92, 92);run("Fill", "slice");makeOval(347, 209, 50, 50);run("Fill", "slice");
run("Select None");
run("Set Measurements...", "area center feret's stack redirect=None decimal=0");
run("Analyze Particles...", "add stack");run("Clear Results");
for(i=0;i<roiManager("Count");i++){roiManager("Select", i);roiManager("Measure");}
	n0x=getResult("XM",i);n0y=getResult("YM",i);n0zPosition = getResult("Slice",i);n0Feret=getResult("Feret");
	if(n0zPosition < nSlices){//check in consecutive slice
		setSlice(n0zPosition + 1);//move to next slice
		setThreshold(128,255);//separate particles from background
		doWand(n0x,n0y);//try to pick up particle at known center axis
		//getValue does not show up in Results table
		n1x = getValue("XM");n1y=getValue("YM");n1Feret=getValue("Feret");
		if((abs(n1x-n0x)<2) && (abs(n1y-n0y)<2)){// center within e.g. 2 pix tolerance
			//centers align, same particle
				print("particle radius is increasing");//deal with increasing radus, update variables
				print("particle radius is decreasing");//past center, store particle data
		}else{//past last slice of particle; possibly deal with new particle in same projected area
			print("lost view of particle ");
	}else{// this was the last slice; wrap up
		print("last slice still showed particle");

Thank you so much!! My advisor and I have been grasping at straws for the past several weeks at how to properly model this and I will definitely implement it as soon as possible. You are truly the best, thank you for all the information and help!

If I combined the stack in Fiji as one tiff file, how can I implement it within the code? Do I have to threshold beforehand and just let the code threshold it for me?

The stack was just a quick way to generate some sample code. You already found out in the 3D Objects Counter @Andrew_Shum suggested, that loading an entire stack clogs your computer memory.
If you use only two separate windows/image files at a time, you use a big for-loop that opens each file in a folder, then process that one and the previous one, set the previous one to point to the current one, close the previous one, opens the new one, then rinse and repeats.
In the forum there are plenty of examples on how to iterate over folder contents.
It is best to start thinking about a strategy and to verify that in each and every file you can single out the disks.

You can binarise your data image by image after loading, prior to sphere detection. If this cannot be done straight forward because of difficult to segment source images, you can build a ‘function’, which does the segmentation and returns a monochrome image containing only binary disks. This workflow allows you to split the large task into smaller ones that can be tested independently.
I guess we’ll be seeing you more often here :wink:

1 Like

Thank you! I’ll be using the forums extensively for the foreseeable future! :slightly_smiling_face: