Z-stack thresholding and colocalization with ImageJ

Hi all!

Looking for some advice about getting any of the ImageJ plugins to play ball for colocalization of largeish z-stacks (~130 slices).

These stacks are images of cells and I want to only colocalize signal within them, ignoring all signal outside.

I’ve done quite a bit of reading on all the methods available, including using rolling ball or local median background subtraction rather than Costes thresholding.

The only method I have gotten to work so far is using Imaris to create a 3D surface around the cell body, then masking the cell so that all pixels outside are 0 and saving the file. I then open that file in Fiji and running the old ‘colocalization threshold’ plugin set to ignore zero-zero pixels. There appears to be no option to ignore zero-zero pixels in any other colocalization plugin.

This seemed to work on most of my images. However, I am now getting very inconsistent thresholding with this plugin. 7/8 images I tried to analyse today were thresholded wrong and had a tM1/tM2 of 1.0. I believe the Costes method used was somehow inaccurate, and fixed in Coloc 2? However, Coloc 2 freezes (or takes many days, I haven’t left it running because it wouldn’t be feasible anyway) when trying to calculate Costes thresholds.

Anyway, how can I calculate a Costes threshold on a z-stack, ignoring zero-zero pixels, then with that threshold generate Mander’s coefficients etc.?

Should I somehow automatically generate an ROI for each slice in the stack (maybe easier because I already have an intensity of 0 outside of the cell) then apply Coloc 2 to the ROI’d stack?

Thank you in advance for your help and your time.



You can use a binary mask or ROIs to define the regions you want to be included in calculations in Coloc2. The instructions are written here:


Thank you!

Actually that was the conclusion I came to as well. Luckily, my images already kind of have ROIs as they were masked in Imaris so that the intensity in the area outside of the cell is all zero.

Now I’m trying to find a way to generate an ROI for each slice in the stack based on this masking, for example by using a threshold of 1.

Any advice on automatically generating ROIs for each slice in a stack based on thresholds would be greatly appreciated. I’ve tried to look into 3DRoiManager but it seems unnecessarily convoluted for what I am trying to do.


You can write a little script to move through you stack slice-by-slice to save ROIs to the ROI manager. But yes - you’ll have to script it. Here are some helpful links to get you started if you haven’t scripted before:

@etadobson Thanks Ellen, I thought this might be the case and started on a macro already. It’s based on https://imagej.nih.gov/ij/macros/ROI_Manager_Stack_Demo.txt with

setThreshold(1, 255);
run(“Create Selection”);

added to create a selection without the zero pixels.

The only problem is that I have slices with all zero pixels, leading to no selection being made and the macro erroring. I’ll figure out a way to move to the colocalization if this occurs.

I appreciate you taking the time to reply to my posts!

1 Like


Sounds like you need to add a ‘check’ using a conditional to avoid this error. If you want to share a short image example and code snippet highlighting this issue - perhaps we can help?


Thanks for your offer to help, I’ve been in the lab this last week and just coming back to my macro today. This is an example of what a slice from the two channels in my images look like (I can upload an actual stack to play with if you’d like). I’ve increased the contrast a lot to show the masking, which is different for each slice and sets all pixels outside my ROI to 0:

The macro I am trying to produce will select a folder, and for each the image stack in the folder run Coloc2 with Costes thresholding, primary to produce Mander’s coefficients for channel 2 and 3 (1 is DAPI).

Here’s what I have so far:

//Z-stack Colocalisation with Costes method using Coloc 2

//Processes a folder of images as long as that folder only contains image files
//Automatically assumes Channel 1 is DAPI
//ROI taken from Channel 2 stack

dir=getDirectory("Choose a Directory - MUST CONTAIN IMAGES ONLY");

//Batch mode not working? Nothing seems to be added to ROI manager when in batch mode


function countFiles(dir) {
	list = getFileList(dir);
	for (i=0; i<list.length; i++) {
		if (endsWith(list[i], "/"))

function processFiles(dir) {
	list = getFileList(dir);
     for (i=0; i<list.length; i++) {
		if (endsWith(list[i], "/"))
		else {
			showProgress(n++, count);
			path = dir+list[i];

function processFile(path) {
//Loop for files in directory begins here
	run("Bio-Formats Importer", "open=path color_mode=Default view=Hyperstack stack_order=XYCZT");

//Get filename for saving results

//Convert to 8-bit, Costes thresholding seems to take hours rather than seconds at >8-bit

//Split channels for analysis
run("Split Channels");

//Rename to be called in Coloc2 (using run("Coloc 2", "channel_1=C2-"+imageTitle" ...etc)  doesn't seem to work)

//Generate z stack ROI
setAutoThreshold("Default dark");

//////////////////////////////////////Not needed?
//if (isOpen("ROI Manager")) {
//		selectWindow("ROI Manager");
//		run("Close");
//	}
for (s=1; s<=nSlices; s++) {
	setThreshold(1, 255);
	run("Create Selection");
	select = selectionType();
	if (select!=-1) {

run("Coloc 2", "channel_1=Ch2 channel_2=Ch3 roi_or_mask=[ROI Manager] threshold_regression=Costes spearman's_rank_correlation manders'_correlation psf=3 costes_randomisations=10");

//Save log for results

if (isOpen("ROI Manager")) {
		selectWindow("ROI Manager");
list = getList("window.titles");
     for (i=0; i<list.length; i++){
     winame = list[i];

//Loop for files in directory ends here


The problem I am currently trying to solve is that it works right up until the colocalization, at which point it seems to be running a separate colocalization for every slice in the image rather than one for the whole stack. I was able to do this using the old “Colocalization Threshold” but with pretty inconsistent results. Could this be caused by having a sepatare ROI for each slice?

Quick update - I tried my macro again without generating any ROIs and it completed successfully (although the results are incorrect because they were performed on the entirety of each slice in the stack rather than just in my ROI).

So, without ROIs through the stack, the result is wrong because of all the zero-zero pixels.

With ROIs, Coloc2 treats each slice as a single image to be colocalized.


Is it the same ROI for each image in the stack? Or is there a unique ROI for each slice in your stack? If this latter case - you might have to do the analysis on a per-slice basis.

Just read through the “How to Use Coloc2” section to see how z-stacks are treated…


What does your output look like in this case?

@etadobson Hmm interesting… I had read that page, but I didn’t notice:

“where the mask image is white (255 pixel value for an 8 bit greyscale image) colocalization will be analyzed for those pixels only. Where it is black (zero pixel value), the pixels will be ignored: not included in the analysis”.

This is exactly what I’m trying to do, but I assumed that Coloc2 was analysing these zero-zero pixels and I needed an ROI because of the following warning I get when running the analysis without an ROI:

“Warning! Zero-zero ratio too high - The ratio between zero-zero pixels and other pixels is large: 0.83. Maybe you should use a ROI.”

And yes, my script generates individual ROIs for each slice in the stack, which I assume is the problem. When I run my script with this code included, it performs a separate analysis and output for each slice in the stack, whereas when I run it without the ROIs it analyses the whole stack.

The output looks fairly similar for both (as each slice isn’t wildly different to the average) but I can tell it’s doing separate analyses for each slice because it produces ~100 outputs when run on a folder with a single image.

Anyway, now I’m starting to think maybe I can safely ignore the warning telling me to use an ROI, because that page claims Coloc2 ignores zero-zero pixels. Unless I have to tell it to do that?

I think I’ve worked it out

I think using ‘roi_or_mask=[one of my channels]’ makes Coloc 2 use the zero-zero pixels as a mask.

I’m going to run my data through this script and see how it goes.

1 Like