Setting autoThreshold from variable

I cannot get the macro below to work. I am trying to edit an older macro to let me give the user all autothreshold options and then ask them to choose one based on numbering. The number then refers to the Threshold method array position. This is called from the array and stored as a variable. Adding the variable in setAutoThreshold() is giving me errors, like:

I’m stumped. I apologise for the clunkiness of the macro, it reflects my poor programming skills. But if someone can help me understand what I’m doing wrong, I’d be really grateful (as you can probably tell by the frustrated printouts!).

/* Macro to count spots inside other spots
 *  Assumes the image is a tif with DAPI in channel1, H2AX in channel2 
 *  It looks for nuclei then counts respective spots for each nucleus.
 *  All results get stored in a concatenating results file.
 *  Plus the mask images are stored (all 2 original images converted to masks)
 *  so you can see what it did.
 *  altered so find maxima works on raw data,not thresholded.
 *  
 *  Input:
 *  Assumes the image is a tif with DAPI in channel1, H2AX in channel2 
 *  
 *  Note- tifs can be produced via another macro to convert from proprietary file format
 *  
 *  Output:
 *  All results get stored in a concatenating results file.
 *  Plus the mask images are stored (all 2 original images converted to masks) so you can check on accuracy.
 */

// altered so find maxima works on raw data,not thresholded.
// var tolerance = 30; removed this and set manually in find maxima.



//Array of global autothreshold methods to call for the user choice based on their order:
ThreshMeths = newArray("Default", "Huang","Huang2","Intermodes","IsoData","Li","MaxEntropy","Mean","MinError","Minimum","Moments","Otsu","Percentile","RenyiEntropy","Shanbhag","Triangle","Yen");

//Variables need to be defined if used between functions and the main macro, not just for sharing between macros.
var Desired_Threshold_Method = 0; 
var ThresholdChoice = "Moments";
var FociChannel =1;
var DapiChannel = 2;
run("Set Measurements...", "area integrated limit display redirect=None decimal=0");
print ("initial settign of var is", ThresholdChoice);
setChannelNames();

dir1 = getDirectory("Choose folder with tif files ");
list = getFileList(dir1);

setBatchMode(true);

// create folders for the tifs
	dir1parent = File.getParent(dir1);
	dir1name = File.getName(dir1);
	dir2 = dir1parent+File.separator+dir1name+"_masks";
	if (File.exists(dir2)==false) {
				File.makeDirectory(dir2); // new directory for mask images
		}

for (i=0; i<list.length; i++) {
	showProgress(i+1, list.length);
	print("processing ... "+i+1+"/"+list.length+"\n         "+list[i]);
	filename = dir1 + list[i];
	if (endsWith(filename, "tif")) {
		path=dir1+list[i];
		run("Bio-Formats Importer", "open=["+filename+"] autoscale color_mode=Default view=Hyperstack stack_order=XYCZT"); 
		originalImage = getTitle();
		roiManager("Reset");
		selectWindow(originalImage);
		run("Make Composite", "display=Composite");
//		Stack.getDimensions(width, height, channels, slices, frames);
//		run("Stack to Hyperstack...", "order=xyczt(default) channels=channels slices=slices frames=frames display=Color");
		run("Duplicate...", "title=nuc duplicate channels=" +DapiChannel);
				print("at line 60 thresholdchoice is ", ThresholdChoice);
		selectWindow("nuc");
		run("Grays");
		BuildThreshStack();  //I am absolutely damned if I can get this to work.  it insists that it the variable ThresholdChoice doesn't exist once I go through this as a function.
	
		selectWindow("nuc");
//		setOption("BlackBackground", true);
//		print("at line 64 thresholdchoice is ", ThresholdChoice);
////		setAutoThreshold(Array.slice(ThreshMeths, Desired_Threshold_Method, Desired_Threshold_Method+1));
		wait(1000);
		print ("outside the function, ThresholdChoice says it is ");
		Array.print(ThresholdChoice);
		setAutoThreshold(+ThresholdChoice+" dark");

//		setAutoThreshold("Moments dark");
		run("Threshold");
		run("Watershed");
		run("Analyze Particles...", "size=48-2500 circularity=0.04-1.00 exclude add");
		run("Blue");
		run("16-bit");
//nuclei now added to ROI manager

//next extract h2ax channel
		selectWindow(originalImage);
		run("Duplicate...", "title=h2ax duplicate channels=FociChannel");
		run("16-bit");
		wait(1000);

//next find H2AX foci per nuc
		selectWindow("h2ax");
		rename(originalImage+"_h2ax");
		for(j=0; j<roiManager("count"); j++) {
			roiManager("select", j);
			run("Find Maxima...", "noise=100 output=[Count]");
//	run("Find Maxima...", "noise="+tolerance+" output=[Point Selection]");
			run("Add Selection...");
			}
		saveAs("Results", dir2+File.separator+originalImage+"_h2ax.xls"); //This is saving as it goes along in case it crashes
		selectWindow(originalImage+"_h2ax");
		rename("h2ax2");
		run("Enhance Contrast", "saturated=0.35");
		selectWindow(originalImage);
		wait(1000);
		close();

		run("Merge Channels...", "c1=h2ax2 c2=nuc create");
		rename("masks");

		saveAs("TIFF", dir2+File.separator+originalImage+"_ThresholdMasks.tif");


	close();
	}
setBatchMode(false);
}

showMessage(" Well, I think we gave them a damn good thrashing there, what what??");

// macro 


//FUNCTION- determine channel order
function setChannelNames() {
Dialog.create("Set which channels are which");  //enable use interactivity
Dialog.addMessage("Which channel is DAPI/ nuclear counterstain in?");
Dialog.addString(" DAPI Channel", DapiChannel);	//add extension
Dialog.addMessage("Which channel are the sub-nuclear spots labelled in?");
Dialog.addString("Foci Channel", FociChannel);	//add extension
Dialog.addMessage("Use channel numbers (eg 1,2) to set the channels to use for determining the nuclei and spots");
Dialog.show();
//StringDapiChannel = Dialog.getString(DapiChannel);  ******Apparently don't need to convert a number from a string- it figures it out itself.
//Dapi = parseFloat(StringDapiChannel);
//StringFociChannel = Dialog.getString(FociChannel);
//Foci = parseFloat(StringFociChannel);
}
//need to set var Foci and var Dapi
//End of function setChannelNames




//FUNCTION Choose global Threshold Method
function BuildThreshStack() {
	run("Auto Threshold", "method=[Try all] white show");
selectWindow("Montage"); 
run("Montage to Stack...", "images_per_row=5 images_per_column=4 border=0");
run("RGB Color");
//drawString("fred'", x, y, "white");
//run("Label...", "format=Text starting=0 interval=1 x=593 y=897 font=17 text=[fred geroge eric] range=1-20");

run("Colors...", "foreground=green background=black selection=red");
 style = 4; // Font.BOLD
 call("ij.gui.TextRoi.setFont", "Sans", 32, style) 
 run("Label...", "format=0000 x=14 y=20 font=32 text=Method use use_text"); 

Call_Choice();
print("I've caught desired thresh from function as", Desired_Threshold_Method);
testy = (ThreshMeths[Desired_Threshold_Method]);
print ("testy at line47 is ", testy); //returns 0, not a string
ThresholdChoice = Array.slice(ThreshMeths, Desired_Threshold_Method, Desired_Threshold_Method+1); //even though I only want one thing from the array it still makes the new variable as an array.
print ("testy at line49 after array slice is ", testy); //returns 0, not a string
print ("the stupid thing works and says it is ");
Array.print(ThresholdChoice);
selectWindow("Stack");
close();

selectWindow("Montage");
close();
}

//ensure var Desired_Threshold_Method and ThresholdChoice are set
//End of function BuildThreshStack



//FUNCTION Call_Choice
function Call_Choice(){
	waitForUser("Browse the Stack to choose the best Method \n number then click OK to input your choice");
	Desired_Threshold_Method = getNumber("Choose your Preferred Threshold Number", 0);
	//Stack.getPosition(slice);
	//print ("Your Choice is...", Desired_Threshold_Method);
}
//ensure global var Desired_Threshold_Method is set
//End of Function Call_Choices
1 Like

@Glyn_Nelson

Maybe a better way would be to use Script Parameters to have the user select the autothreshold method? Then you can use it simply as a String variable to insert in the function call… so something like this:

#@String(label = "Threshold method:",choices={"Default", "Huang", "Huang2", "IsoData", "Li", "MaxEntropy","Mean", "MinError()", "Minimum", "Moments", "Otsu", "Percentile", "RenyiEntropy", "Shanbhag", "Triangle", "Yen"}) method

//run("Threshold...");
setAutoThreshold(method + " dark");

At least if I understood the problem correctly… this would simplify things.

I started off with this option, as you’re right, it is the simplest solution I could think of too. But, a #@ command insists on running at the start of the macro- I couldn’t find a way to call it after presenting the threshold results to the user. I found a possible solution was to have the #@string in a second macro and call that macro. At this point I couldn’t get that to work (I couldn’t figure out how to give it a correct path on my mac), and I also thought it was then asking for trouble down the road when users didn’t put both macros in the same folder.

So I’ve ended up with this more convoluted method- if you and others think my solution is too silly and I should focus on your solution, any pointers on how to call the #@string half way through would be great.

@Glyn_Nelson

Ok… perhaps this older thread has the answer you need to do this:

@imagejan probably has the best advice in this case… as to the feasibility of doing this.

As to your original error message, @Glyn_Nelson:

This is wrong syntax. The expression inside the parentheses cannot start with the + operator. It should read instead:

setAutoThreshold(ThresholdChoice+" dark");

(if ThresholdChoice is a string), or:

setAutoThreshold(""+ThresholdChoice+" dark");

(if ThresholdChoice is a number; but then the option string doesn’t make sense for this command here…).


As long as you have to stick with the IJ1 macro language, your method using getNumber is probably the easiest. (Is waitForUser required in addition to that? This would trigger two dialogs in a row, no?)

As the IJ1 macro language only supports string and number variables, but no Java objects, you cannot use service parameters such as #@ScriptService that would allow doing what you aim at in an easier way.

If you’re fine with switching to a full-fledged scripting language, here’s an example Groovy script that demonstrates how to call a secondary script without the need of having an additional file on disk:

#@ ImagePlus imp
#@ String (visibility=MESSAGE, required=false, value="This is the main dialog. Add any parameters for the main script here") msg
#@ ScriptService scriptService

imp.getProcessor().invert() // do some processing

script = """
\n#@ String (choices={"Moments", "Otsu", "Li"}) thresholdChoice
return thresholdChoice
"""

result = scriptService.run(".groovy", script, true).get().getOutput("result")
println "The final choice was $result"
1 Like

Many thanks for the input. @etadobson, I had essentially got that to work, invoking a python script, but then ran foul of understanding how to correctly write the input in that! I started from :

#@ String(label="Threshold Method", required=true, choices={'otsu', 'huang'}) method_threshold
#@ Dataset data
#@OUTPUT Dataset output
#@ OpService ops
#@ DatasetService ds
 
# Apply an automatic threshold from a given method.
thresholded = ops.run("threshold.%s" % method_threshold, data)
 
# Create output
output = ds.create(thresholded)

found on imagej.net I think. But it is weird. The example as shown only includes two threshold methods which work. If I then start adding the others, it doesn’t work. They are case sensitive and formatted differently to the list you retrieve from imageJ1, which looks like this:

( #@ String(label = "Threshold method:",choices={"Default", "Huang", "Huang2", "IsoData", "Li", "MaxEntropy","Mean", "MinError()", "Minimum", "Moments", "Otsu", "Percentile", "RenyiEntropy", "Shanbhag", "Triangle", "Yen"}) method_threshold

By trial and error I got mean right, but ‘Default’ or ‘default’ doesn’t work nor does ‘IsoData’ or ‘Isodata’. So why does this look at a different list, and what is it/where is it? I would have thought OpsService.run threshold was just invoking setAutoThreshold?

Secondly, if I then call this script half way through another script, I don’t get the dialogue box. It will run the script (eg stick a print function in and it gives the output), but completely silently. I can’t find any documentation on this.

@imagejan, thanks for the comments on the + signs- that was left over by me trying to get the string to be read correctly in the threshold box- I thought it should just work without any +'s? The WaitForUser is required if I want the user to be able to move the stack slider to view the slices. This is frozen when the dialogue question box comes up.

I did more playing yesterday and came up with:

//FUNCTION- choose autoThreshold method
function choose_me() {
Dialog.create("Choose your desired threshold");  //enable use interactivity
 Dialog.addChoice("Best threshold is:", newArray("Default", "Huang","Huang2","Intermodes","IsoData","Li","MaxEntropy","Mean","MinError","Minimum","Moments","Otsu","Percentile","RenyiEntropy","Shanbhag","Triangle","Yen"));
Dialog.show();
   fred = Dialog.getChoice();

//print("diaolog gives fred is ", fred);

}
//need to set var fred
//End of function choose_me

which is fine as long as the text on the stacks are readable. I was thinking to label each slice with the name- I got as far as trying to do this from a list but got stuck with adding the names from a loop:

stamp =  newArray("Default", "Huang","Huang2","Intermodes","IsoData","Li","MaxEntropy","Mean","MinError","Minimum","Moments","Otsu","Percentile","RenyiEntropy","Shanbhag","Triangle","Yen");
keys = newArray(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
stampList = List.fromArrays(keys, stamp)
//Default", "Huang","Huang2","Intermodes","IsoData","Li","MaxEntropy","Mean","MinError","Minimum","Moments","Otsu","Percentile","RenyiEntropy","Shanbhag","Triangle","Yen");
for (i = 1; i < 17; i++) {
	//run("Set Label...", "label="(stamp[i]));
	setSlice(i);
//	Stack.setChannel(i);
//	lbl= Array.slice(stamp,(i),(i+1));
//	Array.print(lbl);
	run("Set Label...", "label=" List.getValue(i));
	
//	
}

As you can see, I got lost trying different methods. I’m sure it should be simple. I’ll leave it for the weekend and have a look again on Monday. Thanks again for your help.

1 Like

No, ops are part of ImageJ2, with the goal to run on images of any type and dimensionality. In contrast, setAutoThreshold is an ImageJ1 macro function, with all the limitations on supported image types and limited 3d processing that comes with the macro language.

You can run Plugins › Utilities › Find Ops… (or press Shift ⇧AltL) to get the list of ops.

You’ll see that there’s no default, as that is specific to the IJ1 implementation. The isoData implementation is the equivalent.


How do you call this script exactly? Did you replicate the way I was showing in my previous post? Or did you run a separate file?

1 Like

Hi. Thanks for the explanation about Ops @imagejan - I assumed they were essentially wrappers for puling in imagej1, not entirely new. I have decided I will start from scratch using ImageJ2/ python- now is a good time to do it as I’m not trying to build this in a rush and I have a bit of time at the moment. Plus, I discovered after getting this to work by manually labelling each slice that the Threshold options provided in setAutoThreshold are different to those in run(“Threshold”). Again I have let myself down making an assumption that they are one and the same list! So there seem to be 3 different lists of global threshold methods that can be called. As it is standing, the incredibly clunky script below works unless you choose Huang2- as that method doesn’t exist in run(“Threshold”). I’m adding it here more for the potential benefit of any other struggling thresholding scripting amateurs!:

Regarding how I called the script, I thought I had copied your recommendation. However, I’ve not kept that version, and the last one I have was calling an ijm version (which also didn’t give me the popup)- that was using runMacro command. In both cases, the py or ijm were called and executed according to the print command I added to them to check.

Glyn.

var fred;

var FociChannel =1;
var DapiChannel = 2;
run("Set Measurements...", "area integrated limit display add redirect=None decimal=0");

setChannelNames();

dir1 = getDirectory("Choose folder with tif files ");
list = getFileList(dir1);

setBatchMode(true);

// create folders for the tifs
	dir1parent = File.getParent(dir1);
	dir1name = File.getName(dir1);
	dir2 = dir1parent+File.separator+dir1name+"_masks";
	if (File.exists(dir2)==false) {
				File.makeDirectory(dir2); // new directory for mask images
		}

for (i=0; i<list.length; i++) {
	showProgress(i+1, list.length);
	print("processing ... "+i+1+"/"+list.length+"\n         "+list[i]);
	filename = dir1 + list[i];
	if (endsWith(filename, "tif")) {
		path=dir1+list[i];
		run("Bio-Formats Importer", "open=["+filename+"] autoscale color_mode=Default view=Hyperstack stack_order=XYCZT"); 
		originalImage = getTitle();
		roiManager("Reset");
		selectWindow(originalImage);
		run("Make Composite", "display=Composite");
//		Stack.getDimensions(width, height, channels, slices, frames);
//		run("Stack to Hyperstack...", "order=xyczt(default) channels=channels slices=slices frames=frames display=Color");
		run("Duplicate...", "title=nuc duplicate channels=" +DapiChannel);
		selectWindow("nuc");
		run("Grays");
		BuildThreshStack();  //I am absolutely damned if I can get this to work.  it insists that it the variable ThresholdChoice doesn't exist once I go through this as a function.
		selectWindow("nuc");

		setOption("BlackBackground", true);
		setAutoThreshold(fred);
		run("Threshold");
		run("Watershed");
		run("Analyze Particles...", "size=48-2500 circularity=0.04-1.00 exclude add");
		run("Blue");
		run("16-bit");
//nuclei now added to ROI manager

//next extract h2ax channel
		selectWindow(originalImage);
		run("Duplicate...", "title=h2ax duplicate channels=FociChannel");
		run("16-bit");
		wait(1000);

//next find H2AX foci per nuc
		selectWindow("h2ax");
		rename(originalImage+"_h2ax");
		for(j=0; j<roiManager("count"); j++) {
			roiManager("select", j);
			run("Find Maxima...", "noise=100 output=[Count]");
//	run("Find Maxima...", "noise="+tolerance+" output=[Point Selection]");
			run("Add Selection...");
			}
		saveAs("Results", dir2+File.separator+originalImage+"_h2ax.xls"); //This is saving as it goes along in case it crashes
		selectWindow(originalImage+"_h2ax");
		rename("h2ax2");
		run("Enhance Contrast", "saturated=0.35");
		selectWindow(originalImage);
		wait(1000);
		close();

		run("Merge Channels...", "c1=h2ax2 c2=nuc create");
		rename("masks");

		saveAs("TIFF", dir2+File.separator+originalImage+"_ThresholdMasks.tif");


	close();
	}
setBatchMode(false);
}

showMessage(" Well, I think we gave them a damn good thrashing there, what what??");

// macro 


//FUNCTION- determine channel order
function setChannelNames() {
Dialog.create("Set which channels are which");  //enable use interactivity
Dialog.addMessage("Which channel is DAPI/ nuclear counterstain in?");
Dialog.addString(" DAPI Channel", DapiChannel);	//add extension
Dialog.addMessage("Which channel are the sub-nuclear spots labelled in?");
Dialog.addString("Foci Channel", FociChannel);	//add extension
Dialog.addMessage("Use channel numbers (eg 1,2) to set the channels to use for determining the nuclei and spots");
Dialog.show();
//StringDapiChannel = Dialog.getString(DapiChannel);  ******Apparently don't need to convert a number from a string- it figures it out itself.
//Dapi = parseFloat(StringDapiChannel);
//StringFociChannel = Dialog.getString(FociChannel);
//Foci = parseFloat(StringFociChannel);
}
//need to set var Foci and var Dapi
//End of function setChannelNames




//FUNCTION Choose global Threshold Method
function BuildThreshStack() {
	run("Auto Threshold", "method=[Try all] white show");
selectWindow("Montage"); 
run("Montage to Stack...", "images_per_row=5 images_per_column=4 border=0");
run("RGB Color");
run("Colors...", "foreground=green background=black selection=red");
 style = 4; // Font.BOLD
 call("ij.gui.TextRoi.setFont", "Sans", 32, style) 

//This is nuts, but I've spent so long trying to use arrays or lists to run through this labelling that I've given up.  
//Writting the next 34 lines took less than 10 minutes. Grrrrrrrrr
//run("Label...", "format=0000 x=14 y=20 font=32 text=Method use use_text"); 
setSlice(1);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Default] range=1-1");
setSlice(2);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Huang] range=2-2");
setSlice(3);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Huang2] range=3-3");
setSlice(4);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Intermodes] range=4-4");
setSlice(5);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[IsoData] range=5-5");
setSlice(6);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[li] range=6-6");
setSlice(7);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[MaxEntropy] range=7-7");
setSlice(8);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Mean] range=8-8");
setSlice(9);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[MinError] range=9-9");
setSlice(10);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Minimum] range=10-10");
setSlice(11);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Moments] range=11-11");
setSlice(12);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Otsu] range=12-12");
setSlice(13);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Percentile] range=13-13");
setSlice(14);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[RenyiEntropy] range=14-14");
setSlice(15);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Shahbhag] range=15-15");
setSlice(16);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Triangle] range=16-16");
setSlice(17);
run("Label...", "format=Text starting=0 interval=1 x=10 y=30 font=32 text=[Yen] range=17-17");


waitForUser("Browse the Stack to choose the best Method \n then click OK to input your choice");
Dialog.create("Choose your desired threshold");  //enable use interactivity
 Dialog.addChoice("Best threshold is:", newArray("Default", "Huang","Huang2","Intermodes","IsoData","Li","MaxEntropy","Mean","MinError","Minimum","Moments","Otsu","Percentile","RenyiEntropy","Shanbhag","Triangle","Yen"));
Dialog.show();
   fred = Dialog.getChoice();

selectWindow("Stack");
close();

selectWindow("Montage");
close();
}

//ensure var fred is set
//End of function BuildThreshStack