Writing macro that exports results to excel file and does not overwrite previous results

I would like to add a command at the end of a macro that exports all imageJ results into an excel spreadsheet and then do a basic calculation in the excel spreadsheet to add all central nuclei counts per fiber. A good example of what am trying to do is explained in this youtube video (https://www.youtube.com/watch?v=M7R6qkdCdI0&t=47s)

Although I am not using the same approach in this video, I would really love to copy the way the results output is exported and analysed in excel.

Your assistance will be highly appreciated.

There is the Results to Excel plugin: https://imagej.net/User:ResultsToExcel

One can use it in a macro.

But one can also import a .csv or .txt file into excel for that matter.

Hi Mwelwa,
I think you may have already been helped with your query, but I just noticed your message and wanted to add that I designed both the morphometry plugin (that your link refers to) and the Excel plugin.
The morphometry plugin contains the required Apache libraries and a custom implementation of the Excel export so that the central-nucleation formula is automatically inserted into the correct place, and with correct reference to the appropriate measurement cell range, into the appendable .xlsx file.
As you have likely summised, the central-nulceation formula is nothing special and can be easily added, manually, after data has been exported. However, since you asked specifically about that function, and if you do see utility in its implentation, for instance in relation to mass-batch processing, then I am happy to help you modify a version of the ‘Read and Write Excel’ plugin for your needs. Alternatively, you can do the calculation from the imagej results table and add it as a static value to the export (avoiding the Excel formula altogether).

If you are happy to do your own manual spreadsheet editing, post export-import, or macro-mediated results calculations, pre-export, then as @schmiedc pointed out, the existing Excel plugin or a regular .csv or .txt import-export may work fine for you.

Kind regards.

1 Like

Hi Antinos,
thanks for your attention to my query.
The Read and Write Excel plugin unfortunately cannot detect central nuclei data generated from running Biovoxxel> Find Speckle plugin (Refer to macro below). The results are output in a log like format which I then convert to a table and export to excel. However, I can’t find a way to avoid overwriting data every time I run the macro. In other words, could you help me modify my macro so that I will be able to transfer the log-like data at the end of the macro into one excel sheet similar to what the Read and Write Excel plugin does, or even better your macro.

Your assistance will be highly appreciated.

Muscle Fiber Segmentation Macro

run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

title = getTitle();

run(“Split Channels”);

selectWindow(title + " (green)");

rename(title+" -muscle fibers");

run(“Gaussian Blur…”, “sigma=2”);

run(“Smooth”);

wait( 2000 );

run(“Morphological Segmentation”);

wait( 2000 );

//setTool(“multipoint”);

call(“inra.ijpb.plugins.MorphologicalSegmentation.segment”, “tolerance=2.0”, “calculateDams=true”, “connectivity=4”);

wait( 2000 );

call(“inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat”, “Watershed lines”);

wait( 2000 );

call(“inra.ijpb.plugins.MorphologicalSegmentation.createResultImage”);

wait( 2000 );

run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

run(“Find Edges”);

run(“Dilate”);

run(“Close-”);

run(“Invert LUT”);

run(“Analyze Particles…”, “size=300-20,000 circularity=0.30-1.00 show=[Bare Outlines] exclude clear summarize”);

wait( 2000 );

run(“Duplicate…”, " ");

run(“Invert LUT”);

run(“Fill Holes”);

setOption(“BlackBackground”, true);

run(“Erode”);

run(“Erode”);

rename(“muscle outline”);

wait( 2000 );

selectWindow(title + " (blue)");

rename(title+" -total nuclei");

run(“Gaussian Blur…”, “sigma=2”);

run(“Smooth”);

setAutoThreshold(“Default dark”);

//run(“Threshold…”);

//setThreshold(10, 255);

run(“Convert to Mask”);

run(“Watershed”);

run(“Analyze Particles…”, “size=10-Infinity show=Masks exclude clear summarize”);

run(“Invert LUT”);

rename(“Tnuclei”);

imageCalculator(“OR create”, “muscle outline”, “Tnuclei”);

run(“Duplicate…”, " ");

run(“Fill Holes”);

rename(“muscleoutline2”)

imageCalculator(“Difference create”, “Result of muscle outline”,“muscleoutline2”);

run(“Invert LUT”);

rename(title+" -Cnuclei");

run(“Analyze Particles…”, “size=10-Infinity show=Masks exclude clear summarize”);

rename(“Cnuclei”);

run(“Invert LUT”);

run(“Speckle Inspector”, “big=muscleoutline2 small=Cnuclei redirect=None exclude speckle font=20”);

Table.sort(“Speckles”);

rename(title+" -Cnucleidistribution");

Cnucleidistribution =getTitle()

saveAs(“Results”, “C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/”+Cnucleidistribution+".csv");

Hi Mwelwa,

Can I just start by saying, I tried the MorphoLibJ segmentation approach on one of my old muscle images and I shed a tear at how good it is. It’s been a while since I worked in the muscle field, but I’m thinking I should remove that youtube video you linked to :sob:.

Anyway, if you add the below code to your macro, replacing the 2 commands after ‘rename(title+" -Cnucleidistribution")’, near the end, then I think the export will work as you want it to (NOTE this method uses the ‘Read and Write Excel’ plugin):

selectWindow("Summary");
CnucleiCount = Table.get("Count", 2);
selectWindow("Speckle List muscleoutline2");
Table.rename("Speckle List muscleoutline2", "Results"); //changing table title to Results for the Excel plugin... we can rename to the oringinal title afterwards
CnucleiPercent = (CnucleiCount/nResults)*100;
setResult("Speckles", nResults, CnucleiPercent);
setResult("Object", nResults-1, "Central Nuclei Percentage");
Table.renameColumn("Speckles", "Central Nuclei");

Cnucleidistribution =getTitle();
run("Read and Write Excel", "title="+Cnucleidistribution+" no_count_column");
IJ.renameResults("Speckle List muscleoutline2");

//saveAs("Results", "C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/"+Cnucleidistribution+".csv");

I’ve only commented out your .csv save, but feel free to delete that line as you see fit. Also, if you prefer to not use the Excel plugin, then we can work out another solution, probably involving opening the specified .csv as a table in imageJ, that we can then amend and export to replace the original.
Also, the above script adds the C/N percentage immediately after the counts list. If that isn’t wanted, then feel free to change parameters (hopefully the approach is enough to show how).

Another minor point: if you are going to be batch processing using your code, you might want to consider closing uneeded windows and tables before the end of the macro. This may not be strictly necessary if you use the GUI batch window (Process>Batch>Macro…), but it just limits the potential for batch problems IMHO.

Kind regards,
Anthony

Hi Antinos,
thank you so much, that worked like a charm!
The only minor query I have is that I’m not so much interested in getting the percentage of central nuclei (as a sum) expressed out of number of fibers, rather I want to know the number or percentage of fibers that have nuclei (regardless of how many) vs those that don’t. So instead of adding up all Cnuclei counts, I would like to obtain a formula to get counts of Cnuclei > 0, and then express this as a percentage of total number of fibers. I can easily do this in Excel, but am wondering whether you can help me figure out a macro for this too so that I can automate the process even more.

Anyways, this is of minor concern for me at the moment. My major issue is that I am having trouble getting the macro to run in Batch mode. I have posted this on the forum, and am just wondering whether you could help me this too.

Thanks for your help thus far.

Hi Mwelwa,

I’m glad I could help. As for the percentage issue, I missed that the summary total of nuclei was the total number, including those sharing a myofibre space. This may have been related to my minimal test image, which didn’t contain any cells with more than one nulceus.

I have recoded the macro to export data with a computed % of centrally nucleated fibres, as you suggest (and as I originally intended). I have also modified the macro to be batch-friendly:


dir = getDirectory("Choose a Directory");
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
	close("Results");
	setBatchMode(true);
	open(filesArray[j]);
	run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");

	title = getTitle();
	run("Split Channels");
	selectWindow(title + " (green)");
	rename(title+" -muscle fibers");
	run("Gaussian Blur...", "sigma=2");
	run("Smooth");
	wait( 2000 );

	setBatchMode("exit and display");
	run("Morphological Segmentation");
	wait( 2000 );
	//setTool("multipoint");
	call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=2.0", "calculateDams=true", "connectivity=4");
	wait( 2000 );
	call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
	wait( 2000 );
	call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
	wait( 2000 );
	window = getTitle();
	close("Morphological Segmentation");
	close("Log");
	selectWindow(""+window);
	setBatchMode(true);
	
	run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
	run("Find Edges");
	run("Dilate");
	run("Close-");
	run("Erode");
	run("Invert LUT");
	run("Analyze Particles...", "size=10-Infinity circularity=0.30-1.00 show=[Bare Outlines] exclude clear summarize");
	wait( 2000 );
	run("Duplicate...", " ");
	run("Invert LUT");
	run("Fill Holes");
	setOption("BlackBackground", true);
	run("Erode");
	//run("Erode");
	rename("muscle outline");
	wait( 2000 );
	selectWindow(title + " (blue)");
	rename(title+" -total nuclei");
	run("Gaussian Blur...", "sigma=2");
	run("Smooth");
	setAutoThreshold("Default dark");
	//run("Threshold…");
	//setThreshold(10, 255);
	run("Convert to Mask");
	run("Watershed");
	run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
	run("Invert LUT");
	rename("Tnuclei");
	imageCalculator("OR create", "muscle outline", "Tnuclei");

	run("Set Measurements...", "area min redirect=None decimal=3");
	run("Analyze Particles...", "size=13.92-Infinity show=Nothing display exclude clear include");
	CnucleiCount = 0;
	MaxColArray = Table.getColumn("Max");
	for (i = 0; i < nResults; i++) {
		if (MaxColArray[i]==255) {
			MaxColArray[i] = 1;
			CnucleiCount++;
		}
	}

	CnucleiPercent = (CnucleiCount/nResults)*100;
	Table.setColumn("Max", MaxColArray);
	setResult("Max", nResults, CnucleiPercent);
	setResult("Area", nResults-1, "CenNuc%");
	Table.renameColumn("Max", "Central Nuclei");
	Table.deleteColumn("Min");
	run("Read and Write Excel", "dataset_label="+title+"-Cnuclei");

	//close all the windows
	run("Close All");
	close("Summary");
	close("Results");
	setBatchMode("exit and display");
}

As far as I could tell, the major issue with the batch implementation before, was the macro needing to interact with the MorphoLibJ graphical user interface. My implementation gets around the problem by temporarily exiting batch mode. This work-around will be a little slower and require more computer resources than a purely batched alternative.

One other thing I changed was the way nuclei are counted. Instead of running the ‘Speckle Inspector’, the macro now uses the Max column of a generated results table, which I believe to be a lot faster (although this may not be noticable on small images). Removing the Speckle plugin call does mean that the nuclei-count per cell is now no longer given, so if you want that readout, feel-free to add that feature back in.

Maybe in general, check the above code against your own, just in-case I absent-mindedly changed something else. Hopefully now that you know the batching issue, you can even modify your own macro to get it working in that mode with the right c/n calculation.

Finally, the above code doesn’t need to be run using the imagej batch GUI (Process>Batch>Macro…), but can be if you remove the main-loop and the ‘open(filesArray[j])’ command. Instead, just run it like a regular macro and it will prompt for a folder to work-on.

Kind regards.

*Edit note: sorry, I’ve edited this for spelling, grammar, and minor-code modification numerous times

Hi Antinos,
thank you so much for your help. You’ve been far too kind.
The macro is definitely much improved compared to what I started with and am sure glad to have your attention.
If you could be so kind and patient to engage me one more time.
In the most recent version of the macro attached below, I have included the speckle inspector plugin to give me a distribution of c/nuclei counts. I couldn’t exactly figure out how to combine the tables into one prior to exporting to excel. My inexperienced solution was to transfer the two output tables to the sample excel file directory. Could you please help me re-write this so that I can export the data as one Excel file with three columns i.e i) counts, ii) cnuclei/fiber iii) binary count (0,255).
I have also noticed that after running the speckle plugin, I am getting a slightly higher number of fibers than initial fiber count (please try this on the attached image). Is there any other method you could suggest for counting speckles within the fibers? At the moment my only option is ignore the speckle fiber count on the assumption that the extra added fibers are tiny pixels and that have no speckles.

Your assistance is greatly appreciated.

Macro

dir = getDirectory(“Choose a Directory”);
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
setBatchMode(true);
open(filesArray[j]);
run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

title = getTitle();
run("Split Channels");
selectWindow(title + " (green)");
rename(title+" -muscle fibers");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
wait( 2000 );

setBatchMode("exit and display");
run("Morphological Segmentation");
wait( 2000 );
//setTool("multipoint");
call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=2.0", "calculateDams=true", "connectivity=4");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
wait( 2000 );
window = getTitle();
close("Morphological Segmentation");
close("Log");
selectWindow(""+window);
setBatchMode(true);

run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
run("Find Edges");
run("Dilate");
run("Close-");
run("Erode");
run("Invert LUT");
run("Analyze Particles...", "size=300-20,000 circularity=0.30-1.00 show=[Bare Outlines] exclude clear summarize");
wait( 2000 );
run("Duplicate...", " ");
run("Invert LUT");
run("Fill Holes");
setOption("BlackBackground", true);
run("Erode");
//run("Erode");
rename("muscle outline");
wait( 2000 );
selectWindow(title + " (blue)");
rename(title+" -total nuclei");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
setAutoThreshold("Default dark");
//run("Threshold…");
//setThreshold(10, 255);
run("Convert to Mask");
run("Watershed");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
run("Invert LUT");
rename("Tnuclei");
imageCalculator("OR create", "muscle outline", "Tnuclei");
run("Duplicate...", " ");
run("Fill Holes");
rename("muscleoutline2")
imageCalculator("Difference create", "Result of muscle outline","muscleoutline2");
run("Invert LUT");
rename(title+" -Cnuclei");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
rename("Cnuclei");
run("Invert LUT");
run("Speckle Inspector", "big=muscleoutline2 small=Cnuclei redirect=None exclude speckle font=20");
Table.sort("Speckles");
rename(title+" -Cnucleidistribution");
Cnucleidistribution =getTitle()
selectWindow("Summary");
CnucleiCount = Table.get("Count", 2);
selectWindow("Speckle List muscleoutline2");
Table.rename("Speckle List muscleoutline2", "Results");
setResult("Speckles", nResults, "#");
setResult("Object", nResults-1, "#");
Table.renameColumn("Speckles", "Central Nuclei1");
Cnucleidistribution =getTitle();
run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.csv]");
selectWindow("Result of muscle outline");
run("Set Measurements...", "area min redirect=None decimal=3");
run("Analyze Particles...", "size=10-Infinity show=Nothing display exclude clear include");

CnucleiCount = 0;
MaxColArray = Table.getColumn("Max");
for (i = 0; i < nResults; i++) {
	if (MaxColArray[i]==255) {
		MaxColArray[i] = 1;
		CnucleiCount++;
	}
}

CnucleiPercent = (CnucleiCount/nResults)*100;
setResult("Max", nResults, CnucleiPercent);
setResult("Area", nResults-1, "CenNuc%");
Table.renameColumn("Max", "Central Nuclei2");
Table.deleteColumn("Min");
Table.deleteColumn("Area");
run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.csv]");
run("Close All");
setBatchMode("exit and display");

}
40AKO muscle WGA, Dapi 210619_TileScan 4_s25.tif (3.6 MB)

Hi Mwelwa,

Give this one a go:

dir = getDirectory("Choose a Directory");
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
	setBatchMode(true);
	open(filesArray[j]);
	run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
	
	title = getTitle();
	run("Split Channels");
	selectWindow(title + " (green)");
	rename(title+" -muscle fibers");
	run("Gaussian Blur...", "sigma=2");
	run("Smooth");
	wait( 2000 );
	
	setBatchMode("exit and display");
	run("Morphological Segmentation");
	wait( 2000 );
	call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=2.0", "calculateDams=true", "connectivity=4");
	wait( 2000 );
	//waitForUser("Press okay when segmentation is ready.");	//include this pause for large images, or increase the 'wait' time
	call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
	wait( 2000 );
	call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
	wait( 2000 );
	window = getTitle();
	close("Morphological Segmentation");
	close("Log");
	selectWindow(""+window);
	setBatchMode(true);
	
	run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
	run("Find Edges");
	run("Dilate");
	run("Close-");
	run("Erode");
	run("Invert LUT");
	run("Analyze Particles...", "size=300-20,000 circularity=0.30-1.00 show=[Bare Outlines] exclude clear summarize");
	wait( 2000 );
	run("Duplicate...", " ");
	run("Invert LUT");
	run("Fill Holes");
	setOption("BlackBackground", true);
	run("Erode");
	//run("Erode");
	rename("muscle outline");
	wait( 2000 );
	selectWindow(title + " (blue)");
	rename(title+" -total nuclei");
	run("Gaussian Blur...", "sigma=2");
	run("Smooth");
	setAutoThreshold("Default dark");
	//run("Threshold...");
	//setThreshold(10, 255);
	run("Convert to Mask");
	run("Watershed");
	run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
	run("Invert LUT");
	rename("Tnuclei");
	imageCalculator("OR create", "muscle outline", "Tnuclei");
	run("Duplicate...", " ");
	run("Fill Holes");
	rename("muscleoutline2");
	imageCalculator("Difference create", "Result of muscle outline","muscleoutline2");
	run("Invert LUT");
	rename(title+" -Cnuclei");
	run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
	rename("Cnuclei");
	run("Invert LUT");

	/*
	run("Speckle Inspector", "big=muscleoutline2 small=Cnuclei redirect=None exclude speckle font=20");
	Table.sort("Speckles");
	rename(title+" -Cnucleidistribution");
	Cnucleidistribution =getTitle();
	selectWindow("Summary");
	CnucleiCount = Table.get("Count", 2);
	selectWindow("Speckle List muscleoutline2");
	Table.rename("Speckle List muscleoutline2", "Results");
	setResult("Speckles", nResults, "#");
	setResult("Object", nResults-1, "#");
	Table.renameColumn("Speckles", "Central Nuclei1");
	Cnucleidistribution =getTitle();
				setBatchMode("exit and display");
	//run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.csv]");
	*/

	selectWindow("Result of muscle outline");
	run("Set Measurements...", "area min redirect=None decimal=3");
	run("Analyze Particles...", "size=10-Infinity show=Nothing display exclude clear include add");
	Table.rename("Results", "OriginalResults");
	
	CnucleiCount = 0;
	MaxColArray = Table.getColumn("Max");
	for (i = 0; i < MaxColArray.length; i++) {
		if (MaxColArray[i]==255) {
			MaxColArray[i] = 1;
			CnucleiCount++;
			roiManager("Select", i);
			run("Find Maxima...", "prominence=10 exclude light output=Count");
		} else {
			setResult("Count", i, 0);	//I think it might be faster to add 0s like this, rather than allow the find maxima function to do it outside of the if==255 condition
			updateResults(); //added with an edit to this code after the problem was pointed out to me
		}
	}
	run("Select None");
	roiManager("reset");
	close("ROI Manager");
	selectWindow("Results");
	NucCountArray = Table.getColumn("Count");
	close("Results");
	Table.rename("OriginalResults", "Results");
	Table.setColumn("Nuclei Count", NucCountArray);
	
	CnucleiPercent = (CnucleiCount/nResults)*100;
	Table.setColumn("Max", MaxColArray);
	setResult("Max", nResults, CnucleiPercent);
	setResult("Area", nResults-1, "CenNuc%");
	Table.renameColumn("Max", "Central Nuclei");
	Table.deleteColumn("Min");
	Table.deleteColumn("Area");
	run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx] dataset_label="+title);
	//run("Read and Write Excel", "dataset_label="+title);
	run("Close All");
	close("Results");
	setBatchMode("exit and display");
}

I thought to merge the Speckle result with the other table, but your observation that the overall fibre numbers were different gave me pause. The Speckle Inspector was identifying very small objects that the Analyze particles function was excluding over a threshold, and whilst the small particles could have been removed or the Inspector function could have been applied to a post-size-thresholded mask, I thought it overall better to come up with another solution (some other reasons I may be wrong about). Anyway, the nuclei are now counted in another way, using the Find Maxima Count command. Since this method iterates over a RoiManager, it won’t be any more efficient than the Speckle Inspector… it’s just another approach that works (or seems to for now).

Best regards.

Hi Antinos,
just tried it and am getting the error message below:

Error: Row (8) out of range in line 102:
setResult ( “Count” , i , 0 <)> ;

How can I fix this?

Hey,

It’s a silly error. The test image you provided happened to have a centrally nucleated myofibre in the first row of the results table, but the macro fails otherwise :sweat_smile:. Apologies for not noticing before.

Just add this command immediately after line 102:

updateResults();

Kind regards.

Hi Antinos,
that’s working perfectly well, thank you so much, you’re such a legend!
Attached below is the final macro. I have added minFeret and Area columns to the results output. I was just wondering whether you could help me add a function to calculate the coefficient of variance (VC) of minFeret diameter to analyse muscle fiber diameter variability. VC= 1000 x standard deviation of muscle fiber minimal diameters/mean muscle fiber minimal diameter.

Macro
dir = getDirectory(“Choose a Directory”);
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
setBatchMode(true);
open(filesArray[j]);
run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

title = getTitle();
run("Split Channels");
selectWindow(title + " (green)");
rename(title+" -muscle fibers");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
wait( 2000 );

setBatchMode("exit and display");
run("Morphological Segmentation");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=2.0", "calculateDams=true", "connectivity=4");
wait( 2000 );
//waitForUser("Press okay when segmentation is ready.");	//include this pause for large images, or increase the 'wait' time
call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
wait( 2000 );
window = getTitle();
close("Morphological Segmentation");
close("Log");
selectWindow(""+window);
setBatchMode(true);

run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
run("Find Edges");
run("Dilate");
run("Close-");
run("Erode");
run("Invert LUT");
run("Set Measurements...", "area standard min feret's redirect=None decimal=3");
run("Analyze Particles...", "size=300-20,000 circularity=0.30-1.00 show=[Bare Outlines] exclude clear summarize");
wait( 2000 );
run("Duplicate...", " ");
run("Invert LUT");
run("Fill Holes");
setOption("BlackBackground", true);
run("Erode");
//run("Erode");
rename("muscle outline");
wait( 2000 );
selectWindow(title + " (blue)");
rename(title+" -total nuclei");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
setAutoThreshold("Default dark");
//run("Threshold...");
//setThreshold(10, 255);
run("Convert to Mask");
run("Watershed");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
run("Invert LUT");
rename("Tnuclei");
imageCalculator("OR create", "muscle outline", "Tnuclei");
run("Duplicate...", " ");
run("Fill Holes");
rename("muscleoutline2");
imageCalculator("Difference create", "Result of muscle outline","muscleoutline2");
run("Invert LUT");
rename(title+" -Cnuclei");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
rename("Cnuclei");
run("Invert LUT");

/*
run("Speckle Inspector", "big=muscleoutline2 small=Cnuclei redirect=None exclude speckle font=20");
Table.sort("Speckles");
rename(title+" -Cnucleidistribution");
Cnucleidistribution =getTitle();
selectWindow("Summary");
CnucleiCount = Table.get("Count", 2);
selectWindow("Speckle List muscleoutline2");
Table.rename("Speckle List muscleoutline2", "Results");
setResult("Speckles", nResults, "#");
setResult("Object", nResults-1, "#");
Table.renameColumn("Speckles", "Central Nuclei1");
Cnucleidistribution =getTitle();
			setBatchMode("exit and display");
//run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.csv]");
*/

selectWindow("Result of muscle outline");
run("Set Measurements...", "area standard min feret's redirect=None decimal=3");
run("Analyze Particles...", "size=10-Infinity show=Nothing display exclude clear include add");
Table.rename("Results", "OriginalResults");

CnucleiCount = 0;
MaxColArray = Table.getColumn("Max");
for (i = 0; i < MaxColArray.length; i++) {
	if (MaxColArray[i]==255) {
		MaxColArray[i] = 1;
		CnucleiCount++;
		roiManager("Select", i);
		run("Find Maxima...", "prominence=10 exclude light output=Count");
	} else {
		setResult("Count", i, 0);
		updateResults(); //I think it might be faster to add 0s like this, rather than allow the find maxima function to do it outside of the if==255 condition
	}
}
run("Select None");
roiManager("reset");
close("ROI Manager");
selectWindow("Results");
NucCountArray = Table.getColumn("Count");
close("Results");
Table.rename("OriginalResults", "Results");
Table.setColumn("Nuclei Count", NucCountArray);

CnucleiPercent = (CnucleiCount/nResults)*100;
Table.setColumn("Max", MaxColArray);
setResult("Max", nResults, CnucleiPercent);
setResult("Area", nResults-1, "CenNuc%");
setResult("MinFeret", nResults, "feret_diam");
Table.renameColumn("Max", "Central Nuclei");
Table.deleteColumn("Min");
Table.deleteColumn("StdDev");
Table.deleteColumn("Feret");
Table.deleteColumn("FeretX");
Table.deleteColumn("FeretY");
Table.deleteColumn("FeretAngle");
run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx] dataset_label="+title);
//run("Read and Write Excel", "dataset_label="+title);
run("Close All");
close("Results");
selectWindow("Summary");
run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx] dataset_label="+title);
setBatchMode("exit and display");

}

Hi Mwelwa,

I’ve added a few lines to the end of the code (below is from line 107):

	run("Select None");
	roiManager("reset");
	close("ROI Manager");
	selectWindow("Results");
	NucCountArray = Table.getColumn("Count");
	close("Results");
	Table.rename("OriginalResults", "Results");
	Table.setColumn("Nuclei Count", NucCountArray);

	minFeretArray = Table.getColumn("MinFeret");
	Array.getStatistics(minFeretArray, min0, max0, MFmean, MFstdDev);
	CoV = 1000*(MFstdDev/MFmean);
	
	CnucleiPercent = (CnucleiCount/nResults)*100;
	Table.setColumn("Max", MaxColArray);
	setResult("Max", nResults, CnucleiPercent);
	setResult("Area", nResults-1, "CenNuc%");
	setResult("Max", nResults, "CoV");
	setResult("MinFeret", nResults-1, CoV);
	Table.renameColumn("Max", "Central Nuclei");
	Table.deleteColumn("Min");
	Table.deleteColumn("StdDev");
	Table.deleteColumn("Feret");
	Table.deleteColumn("FeretX");
	Table.deleteColumn("FeretY");
	Table.deleteColumn("FeretAngle");
	run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx] dataset_label="+title);
	//run("Read and Write Excel", "dataset_label="+title);
	run("Close All");
	close("Results");
	selectWindow("Summary");
	Table.rename("Summary", "Results");
	run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx] dataset_label="+title);
	//run("Read and Write Excel", "dataset_label="+title);
	close("Results");
	setBatchMode("exit and display");
}

It also looked like you wanted to export the summary table next to each Results table, so I added that feature at the end. If that isn’t what you wanted, just delete the rename and export commands near the end.

NOTE: it looks like the default standard-deviation function of the Array.getStatistics functin of ImageJ uses the ‘sample’ forumla for std (/n-1), but you might have a case for using the ‘population’ formula (/n). Hopefully you’ll see what I mean when you compare the macro attempt to what excel can do. If you prefer the ‘poplulation’ calculation, then you may have to construct your own macro formula… or use excel.

Kind regards.

Hi Antinos,
just when I thought everything was running perfectly, I noticed that the find Maxima function actually exagerates the Cnuclei count . So I have elected to revert to the Speckle Inspector plugin which seems to correlate well with manual counts. I have also decided to simplify the data output from each image by removing the percentage and variance calculations. I thought it might be easier to do it in Excel on a stack of images. Attached is the most recent version of the macro.
The trouble I’m having with this version is that its not exporting data to Excel after the first image has been analysed (please refer to the error message).
May you please help me fix this?
Also attached is a couple of sample images that you can trial in Batch mode.Error message.txt (2.3 KB) 40AKO muscle WGA, Dapi 210619_TileScan 4_s25.tif (3.6 MB) Project_Image014.tif (2.2 MB)

Macro
dir = getDirectory(“Choose a Directory”);
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
setBatchMode(true);
open(filesArray[j]);
run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

title = getTitle();
run("Split Channels");
selectWindow(title + " (green)");
rename(title+" -muscle fibers");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
wait( 2000 );

setBatchMode("exit and display");
run("Morphological Segmentation");
wait( 2000 );
//setTool("multipoint");
call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=2.0", "calculateDams=true", "connectivity=4");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
wait( 2000 );
window = getTitle();
close("Morphological Segmentation");
close("Log");
selectWindow(""+window);
setBatchMode(true);

run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
run("Find Edges");
run("Dilate");
run("Close-");
run("Invert LUT");
run("Analyze Particles...", "size=300-20,000 show=[Bare Outlines] exclude clear summarize");
wait( 2000 );
run("Duplicate...", " ");
run("Invert LUT");
run("Fill Holes");
setOption("BlackBackground", true);
run("Erode");
run("Erode");
rename("muscle outline");
wait( 2000 );
selectWindow(title + " (blue)");
rename(title+" -total nuclei");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
setAutoThreshold("Default dark");
//run("Threshold…");
//setThreshold(10, 255);
run("Convert to Mask");
run("Watershed");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
run("Invert LUT");
rename("Tnuclei");
imageCalculator("OR create", "muscle outline", "Tnuclei");
run("Duplicate...", " ");
run("Fill Holes");
rename("muscleoutline2");
imageCalculator("Difference create", "Result of muscle outline","muscleoutline2");
run("Invert LUT");
rename(title+" -Cnuclei");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
rename("Cnuclei");
run("Invert LUT");
run("Speckle Inspector", "big=muscleoutline2 small=Cnuclei redirect=None min_object=300 min_speckle_size=10 exclude speckle font=20");
Table.sort("Speckles");
rename(title+" -Cnucleidistribution");
Cnucleidistribution =getTitle();
CnucleiCount = Table.get("Count", 2);
selectWindow("Speckle List muscleoutline2");
Table.rename("Speckle List muscleoutline2", "Results1");

selectWindow("Result of muscle outline");
run("Set Measurements...", "area standard min feret's redirect=None decimal=3");
run("Analyze Particles...", "size=300-20,000 show=Nothing display exclude clear include add");
selectWindow("Results");
NucCountArray = Table.getColumn("Area");
NucCountArray2 = Table.getColumn("MinFeret");
close("Results");
Table.rename("Results1", "Results");
Table.setColumn("Area", NucCountArray);
Table.setColumn("MinFeret", NucCountArray2);
run("Read and Write Excel", "stack_results file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx]"); 
//run("Read and Write Excel", "dataset_label="+title);
run("Close All");
close("Results"); 
setBatchMode("exit and display");

}

Hi Antinos,
I actually managed to figure out what the issue was with the previous macro. Apparently, the macro was selecting the wrong image to analyse for Cnuclei counts after executing the the Imagecaluculator function.
Attached is the working version of that macro.
Finally, Is there a way to modify the macro to work in headless mode?

Macro

dir = getDirectory(“Choose a Directory”);
filesArray = getFileList(dir);
for (j=0; j<filesArray.length; j++) {
setBatchMode(true);
open(filesArray[j]);
run(“Set Scale…”, “distance=185.6670 known=100 pixel=1 unit=µm”);

title = getTitle();
run("Split Channels");
selectWindow(title + " (green)");
rename(title+" -muscle fibers");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
wait( 2000 );

setBatchMode("exit and display");
run("Morphological Segmentation");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance=3.0", "calculateDams=true", "connectivity=4");
wait( 2000 );
//waitForUser("Press okay when segmentation is ready.");	//include this pause for large images, or increase the 'wait' time
//if background is low use tolerance=1, if high use tolerance=3, otherwise 2 works fine for most images
call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat", "Watershed lines");
wait( 2000 );
call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
wait( 2000 );
window = getTitle();
close("Morphological Segmentation");
close("Log");
selectWindow(""+window);
setBatchMode(true);

run("Set Scale...", "distance=185.6670 known=100 pixel=1 unit=µm");
run("Find Edges");
run("Dilate");
run("Close-");
run("Invert LUT");
run("Analyze Particles...", "size=300-20,000 show=[Bare Outlines] exclude clear summarize");
wait( 2000 );
run("Duplicate...", " ");
run("Invert LUT");
run("Fill Holes");
setOption("BlackBackground", true);
run("Erode");
run("Erode");
rename("muscle outline");
wait( 2000 );
selectWindow(title + " (blue)");
rename(title+" -total nuclei");
run("Gaussian Blur...", "sigma=2");
run("Smooth");
setAutoThreshold("Default dark");
//run("Threshold...");
//setThreshold(10, 255);
run("Convert to Mask");
run("Watershed");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
run("Invert LUT");
rename("Tnuclei");
imageCalculator("OR create", "muscle outline", "Tnuclei");
run("Duplicate...", " ");
run("Fill Holes");
rename("muscleoutline2");
imageCalculator("Difference create", "Result of muscle outline","muscleoutline2");
run("Invert LUT");
rename(title+" -Cnuclei");
run("Analyze Particles...", "size=10-Infinity show=Masks exclude clear summarize");
rename("Cnuclei");
run("Invert LUT");

/*
run("Speckle Inspector", "big=muscleoutline2 small=Cnuclei redirect=None exclude speckle font=20");
Table.sort("Speckles");
rename(title+" -Cnucleidistribution");
Cnucleidistribution =getTitle();
selectWindow("Summary");
CnucleiCount = Table.get("Count", 2);
selectWindow("Speckle List muscleoutline2");
Table.rename("Speckle List muscleoutline2", "Results");
setResult("Speckles", nResults, "#");
setResult("Object", nResults-1, "#");
Table.renameColumn("Speckles", "Central Nuclei1");
Cnucleidistribution =getTitle();
			setBatchMode("exit and display");
//run("Read and Write Excel", "file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.csv]");
*/

selectWindow("Result of muscle outline");
run("Set Measurements...", "area standard min feret's redirect=None decimal=3");
run("Analyze Particles...", "size=10-Infinity show=Nothing display exclude clear include add");
Table.rename("Results", "OriginalResults");

CnucleiCount = 0;
MaxColArray = Table.getColumn("Max");
for (i = 0; i < MaxColArray.length; i++) {
	if (MaxColArray[i]==255) {
		MaxColArray[i] = 1;
		CnucleiCount++;
		roiManager("Select", i);
		run("Find Maxima...", "prominence=10 exclude light output=Count");
	} else {
		setResult("Count", i, 0);
		updateResults(); //I think it might be faster to add 0s like this, rather than allow the find maxima function to do it outside of the if==255 condition
	}
}
run("Select None");
roiManager("reset");
close("ROI Manager");
selectWindow("Results");
NucCountArray = Table.getColumn("Count");
close("Results");
Table.rename("OriginalResults", "Results");
Table.setColumn("CNuclei_Count/fiber", NucCountArray);
Table.setColumn("Max", MaxColArray);
Table.sort("CNuclei_Count/fiber");
Table.renameColumn("Max", "Central Nuclei_Y/N");
Table.sort("Central Nuclei_Y/N");
Table.renameColumn("MinFeret", "MinFeret_Diameter(um)");
Table.renameColumn("Area", "Area(um^2)");
Table.deleteColumn("Min");
Table.deleteColumn("StdDev");
Table.deleteColumn("Feret");
Table.deleteColumn("FeretX");
Table.deleteColumn("FeretY");
Table.deleteColumn("FeretAngle");
run("Read and Write Excel", "stack_results dataset_label=Sample_name file=[C:/Users/Delll/Desktop/40AKO Muscle test images/Central nuclei counts/Cnucleidistribution.xlsx]"); 
//run("Read and Write Excel", "dataset_label="+title);
run("Close All");
close("Results"); 
setBatchMode("exit and display");

}

Hey Mwelwa,

Well done for working out your solution. There’s actually a few other minor corrections I would recommend for my Nov 20th posted code, including the following:

open(dir+File.separator+filesArray[j]);  //the original line works (for some reason), but this is safer

I can also see that you are now using the Excel plugin ‘stack_results’ parameter to place new data underneath existing data in the same file. As a very minor point in relation to that function of the plugin, it contains a minor bug such that the export will fail if on the first export only 1 row is exported. After the first export, zero, one, or more rows of data can be exported without issue. It’s a fairly rare bug, so I haven’t committed time to fixing it. I just thought I would let you know here.

As for your question regarding the re-coding of the macro for running in headless mode, I have to admit to not being very familiar with the implementation of this mode. My instinct informed me that your macro would not be headless-compatible, primarily becuase of the reliance on GUI interaction via the MorphoLibJ plugin. Since reading your messages yesterday, I have subsequently tried a few implementations, and indeed the first failure point seemed to be the Morphological Segmentation GUI reliance. I tried a direct ‘ij2 --headless --console -macro’ parameterisation from the cmd terminal, but I have also read that headless can be run via an Xvfb virtual desktop for GUI interaction. I didn’t go down that route for my curiosity testing, but perhaps that would work for you (with the added overhead of virtualisation).
Finally, as stated on https://imagej.net/Headless, your macro could perhaps be recoded as a plugin, in which case it should be possible to use MorphoLibJ libraries (https://github.com/ijpb/MorphoLibJ) and/or code in your own custom implementation of their plugin. Obviously, this last option would require the most work. There may even be a way of calling public methods from the existing plugin via the macro language’s ‘call(“class.method”)’ function (other than what you already have in your version of the macro), although I have my doubts with this option.

I may come back to this topic for my own development but I, currently, may not be the best person to help you implement exactly what you desire. I imagine others have already approached this topic. Perhaps this calls for a new forum post, specific to headless implementation? (and unless others are willing to chip-in here already).

As always, kind regards.