Fiji / ImageJ: Script development for Image Processing

This tutorial is an introduction to the scripting interface of the Fiji application, an open-source and enhanced version of the popular ImageJ program used for scientific image processing. Here you will learn how to write scripts for automated execution of image processing pipelines and batch processing of multiple image files in Fiji.

Step-by-step instructions are provided for developing scripts in Jython, an implementation of the popular Python language for Java platforms. Example scripts are provided for the Jython and BeanShell languages.

Intro & Fiji Programming Tools


Fiji is a stand-alone application that can be downloaded from the Fiji website. It is available for Mac OSX, Windows, and Linux platforms.

Example Scripts Download examples.

The Application Programming Interface (API)

Fiji and ImageJ are written in Java. The application programming interface (API) defines the various Java packages, classes, methods, and constants that a programmer can use for development of custom Fiji & ImageJ scripts. The API is well documented on these public sites:

Fiji provides convenient programming wrappers for these Java classes so you can write your scripts in:

Fiji provides a richer programming environment than ImageJ and it is recommended to use Fiji instead of ImageJ for any script development.

Step-by-step instructions in this tutorial are provided for developing scripts in Jython. The download section contains scripts written in the Jython and BeanShell languages.

The Script Editor

To start the script editor in Fiji go to menu File > New > Script….

Script editor menus:

The Macro Recorder

In the Fiji menu, go to Plugins > Macros… > Record.

The Console Window

In the Fiji menu, go to Window > Console.


Jython is an implementation of the Python 2.7 programming language designed to run on the Java platform.


A variable is used to store values or references to other objects/functions in the computer’s memory.

Let’s create five variables, x, y, my_name, is_it_Monday, and my_list.

x = 10
y = 2.3
my_name = "Santa Claus"
is_it_Monday = False
my_list = [2,3,5,7,11]

Conditionals: if Statements

Often different blocks of code need to be executed depending on a specific condition. This is achieved by using if, elif, else code blocks.

if <Boolean expression1>:
elif <Boolean expression2>:


x = 100
y = 200
if x < y:
    print "x is smaller than y."
elif x == y:
    print "x equals y"
    print "x is larger than y"    


Code Loops

Like most languages, Python and Jython offer loop constructs to repeatedly execute the same code block.

For loops:

for loops are executed a fixed number of iterations. It is possible to exit early but this must be added to the code.

for <item> in <iterator>:
else:  # optional


for i in range(20):
   print i

While loops:

while loops do not have a predetermined number of iterations. They terminate when some condition is evaluated as False.

while <Boolean expression>:
else: # optional


x = -20
y = -5
while (x<0 and y<0):
    print "x=", x, "y=", y
    x = x - y
    y = y + 1
    print "Exited loop with: x:", x, ", y:", y


A function is a code block that groups related statements that perform a specific task or job within a larger program.

In Jython and Python this could look like this:

def add_values(value1, value2):
    result = value1 + value2
    return result

Example for invocation of the function add_values:

sum = add_values(2.3, 4.1)
print sum

Importing packages

Just like Python, Jython provides an easy way to get access to classes and functions that are defined in external packages, outside of your script.

Here’s an example. Let’s say we want to use the ImageProcessor class in our Jython script. The ImageProcessor class is defined in the ij.process Java package so the equivalent Jython import statement would look like this:

from ij.process import ImageProcessor

Script Example

Jython scripts are simple text files that use specific syntax and structure. Jython scripts (or scripts in any other programing language) should not be edited in word processing programs like Microsoft Word which add invisible formatting characters that mess with programming language syntax.

from ij import IJ

def print_info(imp):
    """This function prints basic image info."""
    print "\nTitle:", imp.getTitle() # print title

    # output image dimensions
    print "Width x Height:", imp.getWidth(), "x", imp.getHeight()

# Main code block
imp = IJ.getImage()

Fiji Scripts in Jython

Images in Fiji: ImagePlus, ImageStack, ImageProcessor

Get Current Image

In Fiji, go to File > Open Samples > Blobs.

from ij import IJ                       # find the IJ class

imp = IJ.getImage()                     # get active Image
print "\nTitle:", imp.getTitle()        # output image title

# output image dimensions
print "Width x Height:", imp.getWidth(), "x", imp.getHeight()

Create the script:

In the Fiji Script Editor, go through these steps:

  1. Create a new file (File > New).
  2. Go to Language and select Python.
  3. Copy the script above into the script editor.
  4. Go to File > Save as... and save the file as
  5. Click the Run button.


Started at Tue Jun 04 00:50:07 EDT 2019

Title: blobs.gif
Width x Height: 256 x 254

Image Dimensions

Fiji and ImageJ can handle multi-dimensional images, e.g. volumetric images or time-lapse series, up to five dimensions. Each image ([ImagePlus][#imageplus] object) has at least two dimensions, i.e. width (x-axis) and height (y-axis). In addition, ImagePlus object can have multiple color channels, z-focal planes (called slices), and/or timepoints (called frames). Lastly, images can have different pixel encodings, e.g. 8-bit, 16-bit, 32-bit, RGB that define an image’s dynamic range (e.g. number of distinct intensity values per pixel) and color representation.

The ImagePlus class provides convenient methods to get this information.

from ij import IJ

imp = IJ.getImage()
width = imp.getWidth()            # get number of pixels along x-axis
height = imp.getHeight()          # get number of pixels along y-axis
channel_no = imp.getNChannels()   # get number of channels
slice_no = imp.getNSlices()       # get number of slices
frame_no = imp.getNFrames()       # get number of frames

bitdepth = imp.getBitDepth()      # bits per pixel
bpp = imp.getBytesPerPixel()      # bytes per pixel (there are 8 bits in a byte)    

Image calibration

Many image formats allow inclusion of image annotations (metadata) in addition to the pixel data. An important piece of information is the spatial pixel calibration, e.g. the definition of pixel size in real-world physical units. For example, a pixel may correspond to 200 nanometer in x-y dimension.

Let’s assume that we have a variable imp as reference to an [ImagePlus][#imageplus] object. We can get and set an image’s calibration with the following ImagePlus class methods:

Get copy of image calibration

calibration = imp.getCalibration().copy()

Set image calibration


Creating a New Image

New images can be created by initializing an ImagePlus object with an ImageProcessor object instance. The following ImageProcessor subclasses can be used, depending on bit-depth and color mode of the desired image.

ImageProcessor subclass Mode bits/pixel
ByteProcessor grayscale 8-bit int
ShortProcessor grayscale 16-bit int
FloatProcessor grayscale 32-bit float
ColorProcessor color 8-bit int per channel


from ij.process import ByteProcessor, ShortProcessor,  \
                                     FloatProcessor, ColorProcessor
from ij import ImagePlus

width = 200                                  # in pixel
height = 100                                 # in pixel
bp = ByteProcessor(width, height)            # create ImageProcessor
imp_bp = ImagePlus("New 8-bit image", bp)    # create ImagePlus with specific title and ImageProcessor object                                # show image window

sp = ShortProcessor(width, height)
imp_sp = ImagePlus("New 16-bit image", sp)

fp = FloatProcessor(width, height)
imp_fp = ImagePlus("New 32-bit image", fp)

cp = ColorProcessor(width, height)
imp_cp = ImagePlus("New RGB image", cp)

This script creates four new images, each with 200 x 100 pixels but with different pixel encodings. Take a look at each image window’s header and note the differences.

Duplicating an Image

The Duplicator class provides a convenient way to create an exact copy of an existing ImagePlus object.

from ij import IJ
from ij.plugin import Duplicator

imp = IJ.getImage()
copied_imp = Duplicator().run(imp)

Note that the duplicated image will be named DUP_<original_title>. You can change the title with the setTitle(string) method.

copied_imp.setTitle("Perfect copy")

Opening and Saving Image Files

Open Images

Fiji can read many different image file formats. This versatility is now provided via the integrated Bio-Formats plugin.

Class/Method Behavior interactive dialog
IJ.openImage() non-interactive, using default image display
loci.formats.ImageReader non-interactive, configurable image display

Save Images

Fiji can also save image files in various common formats, including TIF, OME-TIF, BMP, PNG, JPEG, AVI, etc.).

Class/Method Behavior interactive dialog
IJ.saveAs () non-interactive, simple image writer (TIF, JPEG, GIF, PNG, etc.)
loci.formats.ImageWriter non-interactive, advanced with many formats

Interactive Image Opening and Saving (with Dialog)

from ij import IJ
from import FileSaver

Open file (interactive dialog)

imp =
imp = IJ.getImage()

Save file in original format (interactive dialog)

fs = FileSaver(imp)        # could also use fs.saveAsTiff(), fs.saveAsPng(), etc.

Non-interactive Image Opening and Saving (without Dialog)**

from ij import IJ
import os
from os import path

# Open file from URL or storage device (non-interactive)
filesource = ""
imp = IJ.openImage(filesource)

# Create new output directories and filename
homedir = path.expanduser("~")				# home directory
outputdir = path.join(homedir, "workshop", "images")	# full dir path
print outputdir
if not path.isdir(outputdir):			

# Save file as .tif (non-interactive)
outputfile = os.path.join(outputdir, "blobs-copy.tif")
IJ.saveAs(imp, "tiff", outputfile)
print "Saved file:", outputfile

Check the output in the console window. The image should have been saved to the workshop/images subdirectory in your home directory. If you cannot find your saved file, try this:

Note that file paths use \ on Windows but / on Linux and OSX platforms. Avoid hardcoding the \ or / and use os.path functions to create platform appropriate path names.

The command can be used to execute many of the commands available through the Fiji graphical user interface.

Basic syntax:, command, option)

The Macro Recorder is an excellent tool to convert a function accessible via the Fiji menu into an statement. Let’s try it.


  1. Ensure that the Macro Recorder window is open. If not, go to Plugins > Macro > Recorder.
  2. In the Recorder window’s Record drop-down menu, select BeanShell.
  3. In the Recorder window select and remove any code.
  4. In Fiji, go to File > Open Samples > Boats
  5. Go to Process > Filters > Gaussian Blur, a dialog will pop up:
    • Enter a Sigma value of 5.
    • Click Ok.
  6. Go to File > Save As… > Tiff.
  7. Browse to your workshop/images directory (if it does not exist yet, create it), use blurry_boats.tif as file name and click Save.
  8. In the Recorder window, click Create. This should bring up the Script Editor window with a new script file containing the code copied from the Recorder. If you see more than three lines of code, remove everything that starts before imp = IJ.openImage(…..

Convert to Jython script

  1. In the Script Editor , go to Language, and select Python.
  2. Insert an from ij import IJ statement at the top of the script.
  3. Remove semicolons (;) at the end of the last three lines so your script looks like this (ignore the file path; yours will be different):

    from ij import IJ
    imp = IJ.openImage(""), "Gaussian Blur...", "sigma=5")
    IJ.saveAs(imp, "Tiff", "/Users/mst3k/blurry_boats.tif")
  4. Save your script as Close all image windows.

  5. Run the script.

Check your workshop/images directory for the blurry_boats.tif file.

Working with ImageStack

In Fiji, go to File > Open Samples > Mitosis. This image has the following dimensions:

The multi-dimensional image (represented by an ImagePlus object imp) contains an ImageStack object to manage the different image planes. An ImageStack contains a group of ImageProcessors of the same type and size.

The ImageStack object of an ImagePlus object (imp) can be retrieved like so:

stack = imp.getStack()

The following code provides a reference to the ImageProcessor for a particular channel, slice, and frame in the imp ImagePlus object:

index = imp.getStackIndex(channel_no, slice_no, frame_no)
stack = imp.getStack()
ip = stack.getProcessor(index)

Conversely, we can replace an ImageProcessor in an ImageStack (referenced by the stack variable) like so:

stack.setProcessor(new_ip, index)     # provide new ImageProcessor object, and stack position to place it

Important: The new ImageProcessor object has to be of the same type, width, and height as all the other ImageProcessors in the stack.

Regions-of-Interest (ROIs)

ROIs define groups of pixels in an image. An ROI can be used to:

ROI Types (classes):

ROIs can be managed directly or through the RoiManager class.

ROI: Cropping an Image

In Fiji, go to File > Open > Samples > Clown

from ij.gui import Roi
from ij import IJ
from ij.plugin import Duplicator

# get current image
imp = IJ.getImage()

# create rectangular ROI centered in image
rel_size = 0.5					# 0.0 < rel_size < 1.0
width = int(rel_size * imp.getWidth())
height = int(rel_size * imp.getHeight())
top_left_x = int(0.5 * (imp.getWidth() - width))
top_left_y = int(0.5 * (imp.getHeight()- height))
roi = Roi(top_left_x, top_left_y, width, height)

# copy image, set ROI on copied image, crop, set title and show image
cropped_imp = Duplicator().run(imp)
cropped_imp.setRoi(roi), "Crop", "")		# modifies passed ImagePlus object!

Run the script in the Script Editor.

Fill ROI with a Given Value

from ij import IJ, ImagePlus  
from ij.process import FloatProcessor  
from array import zeros  
from random import random  
from ij.gui import Roi, OvalRoi  

# Create a new ImagePlus filled with noise  
width = 1024  
height = 1024  
pixels = zeros('f', width * height)  
for i in xrange(len(pixels)):  
    pixels[i] = random()  
fp = FloatProcessor(width, height, pixels)  
imp = ImagePlus("Random", fp)  

# Fill a rectangular region of interest with a value of 2.0  
roi = Roi(40, 100, 400, 300)  

# Fill a oval region of interest with a value of -2.0  
oroi = OvalRoi(500, 300, 150, 550)  
fp.fill(oroi.getMask())  # Attention! Required for non-rectangular ROIs

Changing ROI Properties

The appearance of an ROI object (roi) can be changed.

Border Color

from java.awt import Color

Border Width


Fill Color

from java.awt import Color

Set ROI Position

The positioning of an ROI object (roi) can be set for a specific channel, slice, frame in a given image (imp) with multi-dimensional ImageStack.

index = imp.getStackIndex(channelNo, sliceNo, frameNo)

Note: If index is 0 (default), the Roi applies to all image planes in the stack.

Particle Analysis

  1. In the Macro Recorder window’s Record drop-down menu, select BeanShell. Remove any code.
  2. In Fiji, go to File > Open Samples > Blobs
  3. Go to Process > Filters > Median…
    • Set Radius : 2
    • Click Ok.
  4. Go to Process > Binary > Options…
    • Check Black background box.
    • Click OK.
  5. Go to Image > Adjust Threshold…
    • Select Default and Red in drop-down menu
    • Uncheck Dark background box.
    • Click Apply.
  6. Go to Process > Binary > Watershed
  7. Go to Analyze > Set Measurements
    • Select options as shown.
    • Click Ok.
  8. Go to Analyze > Analyze Particles…
    • Select options as shown.
    • Click Ok.

Convert the Macro recordings into a Python script, remember to do the following in the Script Editor:

The script should look like this:

from ij import IJ

imp = IJ.openImage(""), "Median...", "radius=2"), "Options...", "iterations=1 count=1 black")
IJ.setAutoThreshold(imp, "Default"), "Convert to Mask", ""), "Watershed", ""), "Set Measurements...", "area mean min centroid integrated display redirect=None decimal=3"), "Analyze Particles...", "size=0-Infinity display exclude clear summarize add")

Save the script as, and run the script.

You should see the following results tables.


The RoiManager class implements the Analyze > Tools > ROI Manager function. The Particle Analyzer can also use the Roi Manager to store ROIs identified during the analysis, see Particle Analysis.

Using the RoiManager class in scripts:

Get reference to system default instance

from ij.plugin.frame import RoiManager
rm = RoiManager.getInstance2()

Get number of ROIs managed by RoiManager

count = rm.getCount()

Get all ROIs as a list

rois = rm.getAllRoisAsArray()

Get specific ROI at specific index position

roi = rm.getRoi(index)

Remove all ROIs



roi = OvalRoi(100, 150, 50, 50)

Run operation on ROI: combine, select, save, open, etc.



The ImageStatistics class provides access to pixel-based statistics, including the histogram, of an entire image or ROI selection in an image.

Common statements:

Define measurement options: bitwise OR combination of constants

from ij.process import ImageStatistics as IS
options = IS.MEAN | IS.AREA | IS.STD_DEV  # many others

Measure entire image (imp)

imp.setRoi(null)   # remove any ROI from image
stats = imp.getStatistics()

Measure particular ROI (roi) in image (imp)

stats = imp.getStatistics()

Get histogram for image (imp) as a list of intensity counts:

stats = imp.getStatistics()
histo_values = stats.getHistogram()


The ResultsTable class is used for storing measurement results and strings as columns of values.

Common statements:

Get default table used by Analyze > Measure

from ij.measure import ResultsTable
rt = ResultsTable.getResultsTable()

Create new empty table

rt = ResultsTable()

Show table with title. The title Results is reserved for default table for Analyze > Measure."My Results")	# passed argument becomes table title

Get number of table rows


Get list of float values in column (by column header)

col_index = rt.getColumnIndex("Area")

Add value to specific column in last row

rt.addValue("Area", 23.0)

Delete column


Delete row (by row index)


Custom Dialog Windows: Collecting User Input

Custom language agnostic dialogs can be created using the SciJava@Parameter annotation.

Data type Widget type Available styles
boolean / Boolean checkbox
byte / short / int / long numeric field slider / spinner / scroll bar
Byte / Short / Integer / Long numeric field slider / spinner / scroll bar
Float numeric field slider / spinner / scroll bar
BigInteger / BigDecimal numeric field slider / spinner / scroll bar
char / Character / String text field text field / text area / password
Dataset ImagePlus (>=2 images) triggers a dropdown list
ColorRGB color chooser
Date date chooser
File file chooser open / save / file / directory / extensions



# @String(label="Please enter your name",description="Name field") name
# @OUTPUT String greeting

greeting = "Hello, " + name + "!"

When you run the script, a dialog will pop up asking for your input. Type in a name (any character sequence) and click OK. The output message will be shown in a separate window.

Batch Processing of Image Files

Often we want to apply the same image processing method to a set of images.

Simple Template:

# @ File (label="Input directory", style="directory") inputdir
# @ File (label="Output directory", style="directory") outputdir

from ij import IJ
import os
from os import path

def process_file(f):
    print "Processing", f
    imp = IJ.openImage(f), "Gaussian Blur...", "sigma=2");
    return imp

def save_as_tif(directory, image):
    title = image.getTitle().split(".")[0] + "-blurred.tif"
    outputfile = path.join(outputdir, title)
    IJ.saveAs(image, "TIFF", outputfile)
    print "Saved", outputfile

# Main code
inputdir = str(inputdir)
outputdir = str(outputdir)
if not path.isdir(inputdir):
    print inputdir, " is does not exist or is not a directory."
    if not path.isdir(outputdir):
    filenames = os.listdir(inputdir)
    tif_files = [f for f in filenames if f.split(".")[-1] == "tif"]
    for tif_file in tif_files:
        fullpath = path.join(inputdir, tif_file)
        imp = process_file(fullpath)
        save_as_tif(outputdir, imp)
    print "Done.\n"

Installing Scripts as Plugins

Once you have completed script development, you can install the script in Fiji. It will show up as a selectable entry in the Plugins menu.

There are two install options:

After installing the script as plugin, restart Fiji.

Go to File > Plugins and verify that the script is available. The _ and file extension are stripped from the plugin name. Example: is installed as Basic Crop



Project 1: Modify the Get Current Image example script to print the number of channels, number of focal planes (z), and number of timepoints. Open images of your choice from Fiji > Open Samples and compare the output when running the script.

Project 2: Open the Macro Recorder, delete any code in the recorder pane. Record the following steps and convert the macro recording into Jython script. Hint:

  1. Go to File > Open Samples > Fluorescent Cells.
  2. Split the channels (Image > Color > Split Channels).
  3. Merge the channels again as Composite image, but swap the red and green channel.

Project 3: Implement the crop ROI example script with functions.

Project 4: Modify the processing function in Batch Processing example script to apply a different image filter. Hint:


Project 5: Modify the crop ROI example script to have it create multiple cropped images of various sizes in a code loop. Hint: For loops

Project 6: Modify the processing function in Batch Processing example script to prompt user for sigma value to be used by the Gaussian filter. Hint: Custom Dialog Windows. The custom sigma value should be passed from the main code block as an argument to the process function. Hint: Functions

Project 7: Let’s try to improve the particle analysis by implementing the following features:

# @ Float (label="Min particle size", value=50) min_size
# @ Float (label="Max particle size", value=200) max_size
# @ Boolean (label="Save ROIs", value=false) save_rois
# @ File (label="Output directory", style="directory") outputdir

from ij import IJ
from ij.plugin import Duplicator
from ij.plugin.frame import RoiManager
from ij.measure import ResultsTable
from ij.process import ImageStatistics as IS
from os import path

# open image and create copy
original = IJ.openImage("")
imp = Duplicator().run(original)
imp.setTitle("Mask")							# rename the copy, "Median...", "radius=2");, "Options...", "iterations=1 count=1 black")
IJ.setAutoThreshold(imp, "Default"), "Convert to Mask", ""), "Watershed", "")					# break up particle clumps, "Set Measurements...", "area mean min centroid integrated display redirect=None decimal=3")
# hardcoded: "size=50-Infinity display exclude clear summarize add"
moptions = "size=" \
	+ str(min_size) + "-" + str(max_size) \
	+ " display exclude clear summarize add", "Analyze Particles...", moptions)

# get default instance of RoiManager(used by ParticleAnalyzer)
rm = RoiManager.getInstance2()

# get list of all ROIs     
rois = rm.getRoisAsArray()

# set measurement options

# create Results Table
rt = ResultsTable()

# iterate over all ROIs
for roi in rois:
    stats = original.getStatistics(options)      # measure
    rt.incrementCounter()                        # advance row counter

    # add values to various columns
    rt.addValue("Label", original.getTitle())
    rt.addValue("Mean", stats.mean)
    rt.addValue("Area", stats.area)
    rt.addValue("Centroid X", stats.xCentroid)
    rt.addValue("Centroid Y", stats.yCentroid)

# show custom table"Blob Results")

# save contents of ResultsTable object as .csv file
outputdir = str(outputdir)
resultsfile = path.join(outputdir, "blobs.csv")
print "Results file:", resultsfile

if save_rois:
    roifile = path.join(outputdir, "")
    rm.deselect()                                # ensure all ROIs rather than selected are saved
    rm.runCommand("Save", roifile)


Project 8: Modify the processing function in Batch Processing example script to apply a median filter to a circular shaped ROI in the center of the image. The ROI diameter should be half of the width or height (whichever is smaller) of the image. The radius of the median filter should be requested from the user. Hint: Setting ROI, Hint: Recording filter functions, Hint: Custom Dialog Windows

Project 9: Let’s create a script that performs the following operations on a multi-dimensional ImageStack:

from ij import IJ
from ij import ImagePlus
import os
from os import path

imp = IJ.openImage("")

channel_no = imp.getNChannels()              # last channel
slice_no = 1 + imp.getNSlices() // 2         # integer division to get center slice
stack = imp.getStack()                       # get ImageStack object
calibration = imp.getCalibration().copy()    # get pixel calibration

# create output dir workshop/images in home directory if it does not exist
outputdir = path.join(path.expanduser("~"), "workshop", "images")
print outputdir
if not path.isdir(outputdir):

# iterate over all frames and save central slice of last channel
for frame_no in range(1, imp.getNFrames() + 1):
    stack_index = imp.getStackIndex(channel_no, slice_no, frame_no)
    ip = stack.getProcessor(stack_index)

    # remove extension from image title and add frame no as suffix
    title = imp.getTitle().split(".")[0] + "-" + str(frame_no)
    single_imp = ImagePlus(title, ip)

    # create file name with absolute path (incl directory name) and save
    outputfile = path.join(outputdir, title)
    IJ.saveAs(single_imp, "Tiff", outputfile)

Project 10: Modify Project 9 to include the following features:

Download Scripts

Download examples.


Fiji Scripting

General Scripting