Setting the same auto threshold for all images in a batch processing macro

imagej
batch-processing
macro
segmentation

#1

Hi,

I have built a macro that counts the total number of nuclei, then counts the number of nuclei which also have green cytoplasm and then the number of nuclei which also have red cytoplasm. Basically, giving me values to work out percentage purity of my samples across multiple time points. However, some of my images don’t have green cells in them (and others don’t have red cells). My problem is that now my macro sets an auto threshold for each image individually therefore when no red cells are present the threshold is set way too low and all nuclei are thought to be red.

To stop this, I think I want to set the same threshold for all my images, they are all imaged in the same session with the same light intensities, but I want this threshold to be set automatically using the same images. Is there a way to create an auto threshold from multiple images then save the threshold parameters into a variable which I can call later in the macro?

Thank you for your help in advance!
Tom


Auto thresholding works on crop but not whole image
#2

Can you post your macro? After setting the threshold, even when using an automated method, you should have the opportunity to retrieve the values before it is segmented. If you do this, you can reuse the same values to conduct a “manual” threshold for the subsequent images.


#3

Hi Andrew,

Thank you for taking a look! My macro is below. I think I can work out how to pull the threshold values out of a single image to set a manual threshold. However, is it possible to use multiple input images to set that threshold value so that I don’t have to manually select an image with both red and green cells in it as the first image?

 // @File(label = "Input directory", style = "directory") input
// @File(label = "Output directory", style = "directory") output
// @String(label = "File suffix", value = ".zvi") suffix

/*
* Macro to measure red intensity only within the nuclei of cells with a green cytoplasm. Batch processed for multiple images in a folder/subfolders.
*/
processFolder(input);
	
// function to scan folders/subfolders/files to find files with correct suffix
function processFolder(input) {
	Table.create("outputTable"); //creates a new blank table 
	list = getFileList(input);
	for (i = 0; i < list.length; i++) {
		if(File.isDirectory(input + list[i]))
			processFolder("" + input + list[i]);
		if(endsWith(list[i], suffix))
			processFile(input, output, list[i]);
	}
	//saves results for all images in a single file
	Table.save(output + "/All_Results_Table.csv");
	run("Close");
	print("Job done!"); 
}

function processFile(input, output, file) {
	setBatchMode(true); // prevents image windows from opening while the script is running
	Table.set("FileName", Table.size, file); //sets first column name to image title
	
	
	// reset parapemters to measure
	run("Set Measurements...", "area redirect=None decimal=3");
	// open image using Bio-Formats
	run("Bio-Formats", "open=[" + input + "/" + file +"] autoscale color_mode=Default rois_import=[ROI manager] view=Hyperstack stack_order=XYCZT");
  	// run("Bio-Formats Importer", "open=[" + input + "/" + file +"] autoscale color_mode=Default view=Hyperstack stack_order=XYCZT");
	
	//Scale image
	run("Set Scale...", "distance=3.0910 known=1 pixel=1 unit=micron");
	
	// create green cytoplasmic selection
	run("Split Channels");
	selectWindow("C2-input/" + file); // C2 is green 
	run("8-bit"); // masks need 8 bit grey scale images
	run("8-bit");  		
	
	// set loose threshold to ensure 100% of positive nuclei covered 
	run("Gaussian Blur...", "sigma=10");
	setAutoThreshold("Huang dark");
	setOption("BlackBackground", false);
	run("Convert to Mask");
	run("Fill Holes");
	rename("seed");
	
	// set nuceli mask
	selectWindow("C1-input/" + file); // C1 is DAPI channel
	run("Duplicate...", " "); // duplicate DAPI image so orinigal available for both red and green channels
	rename("C1-input/" + file + "_Red"); //rename duplicate for red channel later
	selectWindow("C1-input/" + file); // select the DAPI channel for all total nuclei count and then green count
	run("8-bit");
	run("8-bit"); // Thresholds need 8 bit images
	
	run("Subtract Background...", "rolling=50");
	//run("Gaussian Blur...", "sigma=2");
	// create binary image 
	setAutoThreshold("Huang dark"); // good algo for nuclei 
	setOption("BlackBackground", false);
	run("Convert to Mask");
	run("Fill Holes");
	run("Watershed");
 	rename("mask"); 

	selectWindow("mask");
	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nTotalROI = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("TotalCount", Table.size - 1, nTotalROI);
	//Table.update("outputTable");
	// create new table for results col 1:image name, col 2: total nuclei, col 3: green nuclei, col 4: red nuclei 
	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                     (roiManager("reset"));
		}
	//removes nuclei that arent touching cytoplasm and keep all nuclei in contact
	run("BinaryReconstruct ", "mask=[mask] seed=[seed] create white");
	selectWindow("Reconstructed");

	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nGreenROIs = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("GreenCount", Table.size - 1, nGreenROIs);

	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                     (roiManager("reset")); 
		}
	//add red channel count
	// create red cytoplasmic selection
	selectWindow("C3-input/" + file); // C3 is red 
	run("8-bit"); // masks need 8 bit grey scale images
	run("8-bit");  		
	
	// set loose threshold to ensure 100% of positive nuclei covered 
	run("Gaussian Blur...", "sigma=10");
	setAutoThreshold("Huang dark");
	setOption("BlackBackground", false);
	run("Convert to Mask");
	run("Fill Holes");
	rename("seed");
	
	//use duplicate of DAPI to create new DAPI mask
	selectWindow("C1-input/" + file + "_Red");  
	run("8-bit");
	run("8-bit"); 
	
	run("Subtract Background...", "rolling=50");
	//run("Gaussian Blur...", "sigma=2");
	// create binary image 
	setAutoThreshold("Huang dark"); // good algo for nuclei 
	setOption("BlackBackground", false);
	run("Convert to Mask");
	run("Fill Holes");
	run("Watershed");
 	rename("mask"); 

 	//removes nuclei that arent touching cytoplasm and keep all nuclei in contact
	run("BinaryReconstruct ", "mask=[mask] seed=[seed] create white");
	selectWindow("Reconstructed");
		
	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nRedROIs = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("RedCount", Table.size - 1, nRedROIs);
	Table.update("outputTable");
	
	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                     (roiManager("reset")); 
		} 
	run("Close All");
	}

#4

So, if I understand correctly, what you are now asking is if there is a way to use multiple images to come up with a consensus (for lack of a better term) of the threshold value. Well, yes. The details depend on how you want to aggregate the values. Do you want to apply the thresholding algorithm to a combined histogram or do you simply want to average the separate threshold values?


#5

Hi Andrew,

Yes, a “consensus” threshold is exactly what I am after! I think the combined histogram would be best because I think it would be more accurate at picking up rare cell populations (i.e. few but bright areas only in a few of the images). Whereas, the average would be skewed by many images with all low pixel values.


#6

In that case, you will want to first open all the input images and concatenate them into a stack. Then you should be able to use the autothresholding feature on the stack histogram. You will probably want to use the macro recorder to verify but I think the command would be something like setAutoThreshold("Huang dark stack").


#7

Thanks Andrew I should be able to work out how to do that!


#8

Below is what I came up with, I’m sure it could be more succinct, but I think it does what I need it to now! Thanks for your help Andrew!

// @File(label = "Input directory", style = "directory") input
// @File(label = "Output directory", style = "directory") output
// @String(label = "File suffix", value = ".zvi") suffix

/*
* Macro to open all images in a folder, append to a stack and set thresholds for each channel
*/

function processFolderToStack(input) { 
	list = getFileList(input);
	for (i = 0; i < list.length; i++) {
		if(File.isDirectory(input + list[i]))
			processFolder("" + input + list[i]);
		if(endsWith(list[i], suffix))
			stackThreshold(input, output, list[i]);
	}
	run("Concatenate...", "all_open open");
	run("Split Channels");	
	
	selectWindow("C1-Untitled");
	run("8-bit");  
	setAutoThreshold("Huang dark stack");
	getThreshold(lower, upper);
	blueLower = lower;
	blueUpper = upper;

	selectWindow("C2-Untitled");
	run("8-bit");  
	setAutoThreshold("Otsu dark stack");
	getThreshold(lower, upper);
	greenLower = lower;
	greenUpper = upper;

	selectWindow("C3-Untitled");
	run("8-bit");  
	setAutoThreshold("Otsu dark stack");
	getThreshold(lower, upper);
	redLower = lower;
	redUpper = upper;
	run("Close");
}

function stackThreshold(input, output, file) {
	setBatchMode(true); 
	run("Bio-Formats", "open=[" + input + "/" + file +"] autoscale color_mode=Default rois_import=[ROI manager] view=Hyperstack stack_order=XYCZT");
}

function processFolder(input) {
	Table.create("outputTable"); //creates a new blank table 
	list = getFileList(input);
	for (i = 0; i < list.length; i++) {
		if(File.isDirectory(input + list[i]))
			processFolder("" + input + list[i]);
		if(endsWith(list[i], suffix))
			processFile(input, output, list[i]);
	}
	//saves results for all images in a single file
	Table.save(output + "/All_Results_Table.csv");
	run("Close");
	print("Job done!"); 
}

function processFile(input, output, file) {
	setBatchMode(true); 
	Table.set("FileName", Table.size, file); //sets first column name to image title
	
	// reset parapemters to measure
	run("Set Measurements...", "area redirect=None decimal=3");
	// open image using Bio-Formats
	run("Bio-Formats", "open=[" + input + "/" + file +"] autoscale color_mode=Default rois_import=[ROI manager] view=Hyperstack stack_order=XYCZT");
  	// run("Bio-Formats Importer", "open=[" + input + "/" + file +"] autoscale color_mode=Default view=Hyperstack stack_order=XYCZT");
	
	//Scale image
	run("Set Scale...", "distance=3.0910 known=1 pixel=1 unit=micron");
	
	// create green cytoplasmic selection
	run("Split Channels");
	selectWindow("C2-input/" + file); // C2 is green 
	run("8-bit"); // masks need 8 bit grey scale images
	run("8-bit");  		
	
	// set loose threshold to ensure 100% of positive nuclei covered 
	run("Gaussian Blur...", "sigma=10");
	setThreshold(greenLower, greenUpper);
	run("Convert to Mask");
	run("Fill Holes");
	rename("seed");
	selectWindow("seed");
	//save(output + "/green" + file + ".png");
	
	// set nuceli mask
	selectWindow("C1-input/" + file); // C1 is DAPI channel
	run("Duplicate...", " "); // duplicate DAPI image so orinigal available for both red and green channels
	rename("C1-input/" + file + "_Red"); //rename duplicate for red channel later
	selectWindow("C1-input/" + file); // select the DAPI channel for all total nuclei count and then green count
	run("8-bit");
	run("8-bit"); // Thresholds need 8 bit images
	
	run("Subtract Background...", "rolling=50");
	//run("Gaussian Blur...", "sigma=2");
	// create binary image 
	setThreshold(blueLower, blueUpper); 
	run("Convert to Mask");
	run("Fill Holes");
	run("Watershed");
 	rename("mask"); 

	selectWindow("mask");
	//save(output + "/blue" + file + ".png");
	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nTotalROI = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("TotalCount", Table.size - 1, nTotalROI);
	//Table.update("outputTable");
	// create new table for results col 1:image name, col 2: total nuclei, col 3: green nuclei, col 4: red nuclei 
	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                 (roiManager("reset"));
		}
	//removes nuclei that arent touching cytoplasm and keep all nuclei in contact
	run("BinaryReconstruct ", "mask=[mask] seed=[seed] create white");
	selectWindow("Reconstructed");
	//save(output + "/Recongreen" + file + ".png");
	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nGreenROIs = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("GreenCount", Table.size - 1, nGreenROIs);
	selectWindow("Reconstructed");
	run("Close");
	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                 (roiManager("reset")); 
		}
	//add red channel count
	// create red cytoplasmic selection
	selectWindow("C3-input/" + file); // C3 is red 
	run("8-bit"); // masks need 8 bit grey scale images
	run("8-bit");  		
	
	// set loose threshold to ensure 100% of positive nuclei covered 
	run("Gaussian Blur...", "sigma=10");
	setThreshold(redLower, redUpper);
	run("Convert to Mask");
	run("Fill Holes");
	rename("seed1");
	//save(output + "/red" + file + ".png");
	//use duplicate of DAPI to create new DAPI mask
	selectWindow("C1-input/" + file + "_Red");  
	run("8-bit");
	run("8-bit"); 
	
	run("Subtract Background...", "rolling=50");
	//run("Gaussian Blur...", "sigma=2");
	// create binary image 
	setThreshold(blueLower, blueUpper);
	run("Convert to Mask");
	run("Fill Holes");
	run("Watershed");
 	rename("mask1"); 
	//save(output + "/blueforred" + file + ".png");
 	//removes nuclei that arent touching cytoplasm and keep all nuclei in contact
	run("BinaryReconstruct ", "mask=[mask1] seed=[seed1] create white");
	selectWindow("Reconstructed");
	//save(output + "/Reconred" + file + ".png");	
	// Count of nuceli in reconstructed image
	run("Analyze Particles...", "size=0-Infinity circularity=0.30-1.00 add"); //parameters to define which ROIs to count as nuclei
	nRedROIs = roiManager("count"); //count number of ROIs idenfited in reconstructed image 
	Table.set("RedCount", Table.size - 1, nRedROIs);
	Table.update("outputTable");
	
	// clear previous image roi!! 
		if (roiManager("count") > 0) { 
                 (roiManager("reset")); 
		} 
	run("Close All");
}

var blueLower = 1;
var	blueUpper = 1;
var	greenLower = 1;
var	greenUpper = 1;
var	redLower = 1;
var	redUpper = 1;
processFolderToStack(input);
processFolder(input);