How to automate image particle analysis by creating a macro in ImageJ

UPDATE: This blog post was updated on the 29th June 2019 to fix syntax errors in the code that would prevent batch mode operating and cause the for loop to fail to execute. It also fixes a logic error that would save the results in a different location and prevent the scale being set if no images were open.


ImageJ is a free, science community driven, and user friendly tool for editing images and extracting useful data for statistical analysis. Using it in a point and click fashion works great however it can be even more powerful when you learn how to automate tasks such as the analysis of multiple images ImageJ to increase the efficiency and reproducibility of your science research. Analysing images for metrics such as the area, perimeter and length of one or many particles can be utilised in many different scenarios from determining the surface area of plant leaves, the size of cells in an image or how many individuals are in an animal herd from satellite imagery. Here I will be exploring how to create a macro in ImageJ to automate the analysis of a series of images to extract the size of food particles generated during ingestion of grass by the Australian plague locust (Chortoicetes terminifera).

Locust gut particles

As part of my Honours year I looked at how the Australian plague locust broke up grass leaves for nutrient absorption by collecting the particles that a locust generated straight after having finished a meal. These food particles were stained with Toludine blue and spread out over a microscope slide for imaging. To process images for particle analysis a manual threshold was set and the particles were visualised as black particles on a white background.

Locust-gut-particles-gif
Food particles extracted from an Australian plague locust after eating grass were spread out on a microscope slide and then converted to a black and white image using a manual threshold in ImageJ

Automating particle analysis

Writing a macro in ImageJ is a simple way to automate a number of steps that you need to repeat when extracting data from a number of images in a reproducible way. Running the “Analyze Particles…” command will number each particle in an image and extracts measurements about each particle such as the area, perimeter, length, etc. In this macro we will be using “Analyze Particles…” to analyse a series of images within a folder and save the information about the particles found in each image.

To start with a macro needs a name. All macros start with the word macro followed by a name in quotation marks and a curly bracket:

macro "Automated-Particle-Analysis" {

Initial setup of the measurements, scale calibration and file names

To make it simple for ImageJ to find and save your images the next thing is to tell ImageJ the location of the folder for it to look for images and the folder where the output results are to be saved. The first two lines creates two variables inputFolder and outputFolder that stores the full path of the name of your chosen folders e.g. C:\Users\Will\Images\ and C:\Users\Will\Results\. The third line gets a list of all the filenames in the input folder and stores it in the variable list.

inputFolder=getDirectory("Choose input folder");
outputFolder=getDirectory("Choose output folder for the results");
list=getFileList(inputFolder);

To make analysis faster we can hide the screen displaying the opening and closing of images using setBatchMode(true). Please note that batch mode can cause your macro not to work since you can only call it once in a macro and it can result in errors if a particular function does not work in batch mode – if in doubt it is safe to remove this second line.

setBatchMode(true);

Using a ‘for loop’ to open and process images one by one

Now if you remember the variable list we created it contains all the filenames found in the input folder. This variable starts at line 0 with the first file name and each following filename is on a new line as shown below.

Image-01.tif
Image-02.tif
Image-03.tif
Image-04.tif

We can use this list of names found in the list variable in many ways such as opening images, creating new file names and determining progress on image analysis. Here we create a ‘for loop’ that will carry out the same set of instructions until a certain condition is met.

for(i=0; i<list.length; i++) { 
 path=inputFolder+list[i];

This for loop starts by initialising the variable i to have a value of zero. We then tell it check if the value of i is less than the length of the number of filenames found in the list variable. Lastly we tell it once it has gone through a cycle of the loop to increase the value of i by 1. Here this instruction is represented by the shorthand i++, although this could also be coded as i=i+1. Each time the for loop is executed, the value of i will increase by one until all the files are processed. Using this structure will allow each file to be processed in exactly the same way.

The first line of our for loop creates a variable path that will take the full name of the current file by concatenating (joining) the variable inputFolder with the current filename specified by list[i] e.g. list[1]=Image01.tif, list[2]=Image02.tif.... For example the name found in the variable inputFolder e.g. C:\Users\Will\Images\ will be joined with the current filename specified by list[i] e.g. “Image-01.tif” in the path variable to become C:\Users\Will\Images\Image-01.tif.

Now that we have the full filename including what folder the file is in we can tell ImageJ to open it while checking to make sure it will open a “.tif” image rather than any other text files or hidden database files. On the next line we use the list variable to show the visual progress of our macro on the ImageJ tool bar.

for(i=0, i<list.length, i++) { 
 path=inputFolder+list[i];
 if(endsWith(path,".tif")) open(path);
 showProgress(i, list.length);

Particle analysis, setting the measurements and calibrating the scale

Providing an image was opened, and this is the first time the for loop has run, we can now specify the scale to set all the images and the measurements to record, such as area and perimeter. To have both the scale and measurements specified within the macro, you can pilot choosing your options with the macro recorder on (Plugins>Macro>Record…), and then replace the two relevant lines beginning with “run” below.

if(nImages>=1) {
 if(i==0) {
  run("Set Scale...");
  run("Set Measurements...");
 }

With the scale and measurements set, we can now do particle analysis:

if(nImages>=1) {
 if(i==0) {
  run("Set Scale...");
  run("Set Measurements...");
 }
 run("Analyze Particles...","size=0-Infinity circularity=0.00-1.00 show=Nothing display exclude clear include record");

Here I specified a range of options in the Analyze Particles command such as including all sizes and shapes of particles, excluding particles on the edges of the image and to record the coordinates of each particle . The easiest way to get this setup exactly how you want it is to pilot the options you want to have included in your results on an image manually while having the macro recorder in ImageJ on (Plugins>Macro>Record…). You can directly copy and paste this line of text into your macro file.

Saving the results using the original filenames

With the analysis done we need to select the results window, save the measurements as a text file and then close the results table and current image.

selectWindow("Results"); 
outputPath=outputFolder+list[i];
fileExtension=lastIndexOf(outputPath,".");
if(fileExtension!=-1) outputPath=substring(outputPath,0,fileExtension);
saveAs("Measurements",outputPath+".csv"); 
run("Close"); //closes Results window
close(); //closes the current image

To prepare names to save the results, we create an outputPathvariable that joins the location of the outputFolder variable with the current filename specified by list[i] e.g. list[1]=Image01.tif, list[2]=Image02.tif.... Before saving the results file, we should also remove the file extension of “.tif” otherwise when saving the results the filename would look like this: Image-01.tif.csv. The variable fileExtension becomes the value of all the characters after and including the full stop e.g. .tif that it finds in the outputPath variable e.g. C:\Users\Will\Results\Image-01.tif. In the next line, ImageJ checks if there is a string of text in the fileExtension variable. And if there is, it will convert the full string of text i.e. C:\Users\Will\Results\Image-01.tif, into a smaller substring without the .tif at the end, and will  overwrite the outputPath variable to become this smaller substring i.e. C:\Users\Will\Results\Image-01.

outputPath=outputFolder+list[i];
fileExtension=lastIndexOf(outputPath,".");
if(fileExtension!=-1) outputPath=substring(outputPath,0,fileExtension);

I have chosen to save this as a comma separated file “.csv” as this is easily opened in a text editor or spreadsheet program such as Excel. To save the file we have concatenated (joined) the full path without the file extension i.e. C:\Users\Will\Results\Image-01 and the type of file we want it saved as “.csv” e.g. C:\Users\Will\Results\Image-01+.csv to give us the full path of the filename C:\Users\Will\Results\Image-01.csv.

saveAs("Measurements",outoutPath+".csv");

By joining different strings of text it gives you a lot of flexibility with how you want to name your files. All you have to do is remember to use + to join the different strings of your desired name and to surround text in quotation marks unless it is a variable. For example if you wanted to have each file appended with the string “Results” to give an output name such as Image-01-Results.csv you could write:

saveAs("Measurements",outputPath+"-Results.csv"

or

saveAs("Measurements",outputPath+"-Results"+".csv"

The finished macro

macro "Automated-Particle-Analysis" {
inputFolder=getDirectory("Choose input folder");
outputFolder=getDirectory("Choose output folder for the results");
list=getFileList(inputFolder);
 
setBatchMode(true);

for(i=0; i<list.length; i++) {
 path=inputFolder+list[i];
 if(endsWith(path,".tif")) open(path);
 showProgress(i, list.length);
 if(nImages>=1) {
  if(i==0) {
   run("Set Scale...");
   run("Set Measurements...");
  }
  run("Analyze Particles...","size=0-Infinity circularity=0.00-1.00 show=Nothing display exclude clear include record add");
  selectWindow("Results");
  outputPath=outputFolder+list[i];
  //The following two lines removes the file extension
  fileExtension=lastIndexOf(outputPath,"."); 
  if(fileExtension!=-1) outputPath=substring(outputPath,0,fileExtension);
  saveAs("Measurements", outputPath+".csv");
  run("Close"); //closes Results window
  close(); //closes the current image
  }
 }
setBatchMode(false);
}

Note that once every image is analysed we stop the batch mode processing by using setBatchMode(false);right before the very last curly bracket.

Quick tips

Often when working with macros you will come across something not working and ImageJ will spit out an error. Key errors to check for are for spelling mistakes of variables or commands, curly brackets are opened { and closed } at the correct places and each statement ends with a semicolon ;.

Additional resources

Built in Macro Functions of ImageJ – this is the go to reference for all the macro functions that you can use in ImageJ.

https://imagej.nih.gov/ij/developer/macro/functions.html

Macro Language – this is an introductory guide to the structure of the ImageJ macro language.

https://imagej.nih.gov/ij/developer/macro/macros.html

4 Comments

    1. Hi Felix,

      Thanks for your interest in adapting the code. You are welcome to reuse it for any purpose.

      You can cite it as:

      Armour, W. (2018, March 25). How to automate image particle analysis by creating a macro in ImageJ [Blog post]. Retrieved May 17, 2019, from https://willarmour.science/how-to-automate-image-particle-analysis-by-creating-a-macro-in-imagej/

      or from my Honours thesis when I originally wrote it:

      Armour, W. (2008). The morphology and physiology of plant tissue following ingestion by the Australian plague locust (Chortoicetes terminifera, Orthoptera) and possible consequences for nutrient assimilation (Honours Thesis). The University of Sydney, Camperdown, NSW, Australia.

      Thanks,

      Will

  1. Hi Will,

    Any advise for resolving this issue?

    Error 1)

    Err
    Memory * 110MB of 8398MB (1%)
    nImages() 1
    getTitle() “C1_01_1_1_DAPI_001.tif”
    inputFolder (g) “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”
    Memory (g) 0
    i (g) * 1
    list (g) * array[24]
    outputFolder (g) * “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”

    — —
    Error: Undefined identifier in line 8:
    ( true ) ;

    Error 2)

    Memory * 99MB of 8398MB (1%)
    nImages() 1
    getTitle() “C1_01_1_1_DAPI_001.tif”
    inputFolder (g) “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”
    Memory (g) 0
    i (g) 1
    list (g) array[24]
    outputFolder (g) “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”

    — —
    Error: Undefined identifier in line 8:
    ( false ) ;

    Error 3)
    Memory * 116MB of 8398MB (1%)
    nImages() 1
    getTitle() “C1_01_1_1_DAPI_001.tif”
    inputFolder (g) “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”
    Memory (g) 0
    i (g) 1
    list (g) array[24]
    outputFolder (g) “/Users/TomHilburn/Library/Mobile Documents/com~apple~CloudDocs/Research /01 RPE/…”

    — —
    Error: ‘;’ expected in line 9:
    for ( i = 0 , i < list . length , i ++ {

    Cheers,

    Tom

    1. Hi Tom,

      Thanks so much for commenting about this issue! I tested it on my computer and discovered there were a few typos that were causing ImageJ to error, as well as some design errors in the original code that did not save files to the set output location. I have made an update to this article to ensure that it works properly for you.

      Some specific errors:

      “setBatchmode(true);” should have read “setBatchMode(true);” (The “m” needed to be capitalised).

      In the for loop you are missing a right parenthesis after the i++. I mistakenly had written the for loop using commas instead of semi colons. Thus this line should look like:

      for(i = 0; i < list.length; i++) {

      While reviewing over this code, I thought you might also like to save the ROI coordinates so if you would like to do that just put these two lines of code after the line that says it will close the results table.

      roiManager("Save", outputPath+"-ROI.zip");
      roiManager("reset");

      If you have any other questions, feel free to ask.

      Cheers,

      Will

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.