// Macro to automatically detect, count, and measure charcoal fragments from a batch of images in ImageJ
// 2. AUTHORS, DATE, AND LICENSE
// Javier Ruiz-Pérez (jruizperez@tamu.edu, javier.ruizperez.academic@gmail.com) - Department of Ecology and Conservation Biology, Texas A&M University; Julie C. Aleman - Centre Européen de Recherche et d’Enseignement des Géosciences de l’Environnement, Centre National de la Recherche Scientifique; Joseph W. Veldman - Department of Ecology and Conservation Biology, Texas A&M University
// This macro is distributed under the terms of a CC BY 4.0 license
// Macro to automatically detect, count, and measure (i.e., area, length, and width) charcoal fragments in a set of images via global thresholding (i.e., classification of pixels into two classes using a cut-off value) and export the results. The output files consist of 1) a CSV file with the parameters set for analysis, date of analysis, and software versions; 2) a CSV file containing the counting and measurement data; and 3) outline image(s) in JPEG or TIFF format. This macro has been written and tested using ImageJ version 1.54d for Microsoft Windows with Java version 1.8.0_345. To use it, download ImageJ from https://imagej.net/ij/download.html, open it, go to Plugins -> Macros -> Run..., and open this macro as a .txt file
// The script has two sections: definition of parameters (section 4.1) and batch analysis (section 4.2). The following parameters are entered in a dialog box:
// 1) Folder containing the images to be analyzed (input) and destination folder to save the output files (output)
// 2) Scale unit of the images (unit), known distance in original unit (distanceUnit), and its equivalent in pixels (distancePixels). To obtain the scale values in ImageJ, 1) open an image with a reference scale such a scale bar (File -> Open...); 2) select the tool Straight Line (i.e., fifth tool from the left on the ImageJ toolbar); 3) draw a line along the length of the scale bar while holding the Shift key in the keyboard; and 4) go to Analyze -> Set Scale... and record the distance in pixels and the known distance values
// 3) Thresholding method (thresholdMethod) and, if Manual method is selected, minimum and maximum pixel values (thresholdMin, thresholdMax, respectively, from 0 to 255) to discriminate the charcoal fragments against the background and non-charcoal particles. Automatic thresholding methods are described at https://imagej.net/plugins/auto-threshold#available-methods. Manual threshold values can be explored in ImageJ by 1) opening an image (File -> Open...); 2) transforming the image to grayscale (Image -> Type -> 8-bit); and 3) directly adjusting threshold values with the Threshold tool (Image -> Adjust -> Threshold...). Note that, when manual thresholding is selected, the macro applies the same minimum and maximum threshold values entered in the dialogue box to all images; in contrast, when an automatic threshold method is selected, it will calculate new threshold values for each image. To learn more about the threshold tool, visit https://imagej.net/ij/docs/guide/146-28.html#sub:Threshold...[T] // 4) Size range (in the set unit, from 0 to Infinity) of the fragments to be counted and measured (sizeMin, sizeMax). Note that fragments touching the edges of the images are excluded from analysis
// 6) Holes filling (holes) to fill holes within charcoal
// 7) Outline images format as JPEG or TIFF (format)
// 4.1. Parameters dialog
// List of parameters and default values
output = "Output folder";
unit = newArray("micron", "mm", "cm");
thresholdAutoMethods = getList("threshold.methods");
thresholdMethod = Array.concat("Manual", thresholdAutoMethods);
separation = newArray("No", "Yes");
holes = newArray("No", "Yes");
format = newArray("Jpeg", "Tiff");
Dialog.create("Select parameters");
Dialog.addDirectory("Folder containing the images to be analyzed:", input);
Dialog.addDirectory("Folder where to save the results:", output);
Dialog.addChoice("Scale unit:", unit);
Dialog.addNumber("Scale distance in original unit:", distanceUnit);
Dialog.addNumber("Scale distance in pixels:", distancePixels);
Dialog.addChoice("Thresholding method:", thresholdMethod);
Dialog.addNumber("Minimum manual threshold (0 to 255):", thresholdMin);
Dialog.addNumber("Maximum manual threshold (0 to 255):", thresholdMax);
Dialog.addNumber("Minimum charcoal size in set unit:", sizeMin);
Dialog.addNumber("Maximum charcoal size in set unit:", sizeMax);
Dialog.addChoice("Watershed separation:", separation);
Dialog.addChoice("Fill holes:", holes);
Dialog.addChoice("Outline image(s) format:", format);
// List of parameters, entered values, date of analysis, and software versions
input = Dialog.getString();
output = Dialog.getString();
unit = Dialog.getChoice();
distanceUnit = Dialog.getNumber();
distancePixels = Dialog.getNumber();
thresholdMethod = Dialog.getChoice();
thresholdMin = Dialog.getNumber();
thresholdMax = Dialog.getNumber();
sizeMin = Dialog.getNumber();
sizeMin2 = Math.pow(sizeMin, 2);
sizeMax = Dialog.getNumber();
sizeMax2 = Math.pow(sizeMax, 2);
separation = Dialog.getChoice();
holes = Dialog.getChoice();
format = Dialog.getChoice();
monthNames = newArray("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
getDateAndTime(year, month, week, day, hour, min, sec, msec);
dateTime = ""+day+"/"+monthNames[month]+"/"+year+" "+hour+":"+min+":"+sec;
versionIJ = getVersion();
versionJava = getInfo("java.version");
// Export parameters, values, date, and versions list
Table.create("Parameters");
selectWindow("Parameters");
listParameters = Array.concat("Folder containing the charcoal images", "Folder containing the results", "Scale unit", "Scale distance in original unit", "Scale distance in pixels", "Thresholding method", "Minimum manual threshold", "Maximum manual threshold", "Minimum charcoal size", "Maximum charcoal size", "Watershed separation", "Fill holes", "Outline image(s) format", "Date and time", "ImageJ version", "Java version");
listParametersSelected = Array.concat(input, output, unit, distanceUnit, distancePixels, thresholdMethod, thresholdMin, thresholdMax, sizeMin, sizeMax, separation, holes, format, dateTime, versionIJ, versionJava);
for (i = 0; i < 16; i++) {
Table.set("Parameter", i, listParameters[i]);
Table.set("Value", i, listParametersSelected[i])
Table.save(output + "Parameters" + ".csv");
// 4.2. Batch to process every image from the input folder and export the results
// Names list from the images in the input folder
listNames = getFileList(input);
listNamesOutput = getFileList(input);
for (i = 0; i < listNamesOutput.length; i++) {
dotIndex = indexOf(listNamesOutput[i], ".");
listNamesOutput[i] = substring(listNamesOutput[i], 0, dotIndex);
Table.create("Table of results");
for (i = 0; i < listNames.length; i++) {
open(input + listNames[i]);
run("Set Scale...", "distance=distancePixels known=distanceUnit unit=unit");
if (thresholdMethod == "Manual") {
setThreshold(thresholdMin, thresholdMax);
setOption("BlackBackground", false);
setAutoThreshold(thresholdMethod);
setOption("BlackBackground", false);
if (separation == "Yes"){
run("Set Measurements...", "area feret's redirect=None");
run("Analyze Particles...", "size=sizeMin2-sizeMax2 show=Outlines display exclude clear");
saveAs(format, output + listNamesOutput[i]);
area = Table.getColumn("Area");
length = Table.getColumn("Feret");
width = Table.getColumn("MinFeret");
selectWindow("Table of results");
for (j = 0; j < count; j++) {
Table.set("Image", totalCount+j, listNamesOutput[i]);
Table.set("Charcoal", totalCount+j, j+1);
Table.set("Area", totalCount+j, area[j]);
Table.set("Length", totalCount+j, length[j]);
Table.set("Width", totalCount+j, width[j]);
Table.setLocationAndSize(100, 100, 750, 750);
selectWindow("Table of results");
Table.set("Image", totalCount, listNamesOutput[i]);
Table.set("Charcoal", totalCount, 0);
Table.set("Area", totalCount, 0);
Table.set("Length", totalCount, 0);
Table.set("Width", totalCount, 0);
Table.setLocationAndSize(100, 100, 750, 750);
Table.save(output + "Results" + ".csv");
// Message of completion/error
if (File.exists(output + "Results" + ".csv") == true) {
showMessage("Analysis completed.");
showMessage("An error has occurred.");