ImageJ2 scripts

Parameters + ImageJ Ops

Curtis Rueden, UW-Madison LOCI
Brian Northan, True North Intelligent Algorithms LLC


Why parameterize?

  • Less code
  • Easier to read
  • Write once, run anywhere!

Go from this:


from ij import IJ, ImagePlus, WindowManager
from ij.gui import GenericDialog

def run():
	# fail if no images are open
	wList = WindowManager.getIDList()
	if wList is None:
		IJ.noImage()
		return
	# build list of image titles
	titles = []
	for w in wList:
		imp = WindowManager.getImage(w)
		titles.append("" if imp is None else imp.getTitle())
	# prompt user for image inputs
	gd = GenericDialog("Label Copier")
	gd.addChoice("Source Image:", titles, titles[0])
	gd.addChoice("Target Image:", titles, titles[0])
	gd.showDialog()
	if gd.wasCanceled():
		return
	source = WindowManager.getImage(wList[gd.getNextChoiceIndex()])
	target = WindowManager.getImage(wList[gd.getNextChoiceIndex()])
	# ensure images are compatible
	if source.getStackSize() != target.getStackSize():
		IJ.error("Source and target images must have same stack size.")
		return
	# copy the labels
	for i in range(1, source.getStackSize()):
		label = source.getStack().getSliceLabel(i)
		target.getStack().setSliceLabel(label, i)

run()
						

To this:


# @ImagePlus source
# @ImagePlus target

from ij import IJ

def run():
	# ensure images are compatible
	if source.getStackSize() != target.getStackSize():
		IJ.error("Source and target images must have same stack size.")
		return
	# copy the labels
	for i in range(1, source.getStackSize()):
		label = source.getStack().getSliceLabel(i)
		target.getStack().setSliceLabel(label, i)

run()
						

Getting started

  • Press the [ key to open the Script Editor
  • Select Templates ▶ Python ▶ Greeting

You should see:


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

# A Jython script with parameters.
# It is the duty of the scripting framework to harvest
# the 'name' parameter from the user, and then display
# the 'greeting' output parameter, based on its type.

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

Supported types

You can use any Java type for a parameter.

Select Templates ▶ JavaScript ▶ Widgets

But only some types appear in the user interface:

boolean | Booleancheckbox
byte | short | int | longnumeric field
Byte | Short | Integer | Longnumeric field
BigInteger | BigDecimalnumeric field
char | Character | Stringtext field
Dataset | ImagePlus (>=2)dropdown list
ColorRGBcolor chooser
Datedate chooser
Filefile chooser (opener)

Some parameters have configurable styles:

Numbers with style="slider"field + slider
Numbers with style="scroll bar"field + scroll bar
String with style="text area"text area
String with style="password"password field
String with choices={"foo", "bar", ...}dropdown list
String with style="radioButtonHorizontal"radio buttons
String with style="radioButtonVertical"radio buttons
File with style="save"file chooser (saver)
File with style="directory"directory chooser
String with visibility=MESSAGElabel (non-editable)

Certain parameters are populated automatically:

Service | Context from the application context
Dataset | ImagePlus from the active image
DataView | DatasetView from the active image
Display | ImageDisplay from the active image
Overlay from the active image's ROI

The ImageJ2 data model is still unstable; some of these will change.

More things are possible in Java code:

  • Support for previews and cancelation
  • Callbacks when inputs change
  • Buttons which trigger callbacks
  • Dynamically add, remove and modify parameters
  • Write your own input widgets!

See the widget-demo tutorial for details.

Intro to ImageJ2 & Ops

In Script Editor, select:
  • Templates ▶ Tutorials ▶ 01 - Intro to ImageJ API
  • Templates ▶ Tutorials ▶ 02 - Load and Display Dataset
  • Templates ▶ Tutorials ▶ 03 - Using Ops

Getting help

Image Processing Examples

  1. Crop N-Dimensional Image
  2. Segment with Ops, Analyze with IJ1
  3. Threshold Hyperslice
  4. Projections
  5. FFT Template Matching

Crop N-Dimensional Image

    Explore data using ImageJ2 Data Model (beta)

    • Print size of each dimension
    • Print axis type of each dimension
  • N-Dimensional cropping with imagej-ops
  • In ImageJ, select File ▶ Open Samples ▶ Confocal Series (2.2MB)
  • In Script Editor, select Templates ▶ Tutorials ▶ Crop Confocal Series

You should see:


# @OpService ops
# @Dataset data
# @UIService ui

# to run this tutorial run 'file->Open Samples->Confocal Series' and make sure that
# confocal-series.tif is the active image

from net.imglib2.util import Intervals
from net.imagej.axis import Axes

# first take a look at the size and type of each dimension
for d in range(data.numDimensions()):
        print "axis d: type: "+str(data.axis(d).type())+" length: "+str(data.dimension(d))

img=data.getImgPlus()

xLen = data.dimension(data.dimensionIndex(Axes.X))
yLen = data.dimension(data.dimensionIndex(Axes.Y))
zLen = data.dimension(data.dimensionIndex(Axes.Z))
cLen = data.dimension(data.dimensionIndex(Axes.CHANNEL))

# crop a channel
c0=ops.image().crop(img, Intervals.createMinMax(0, 0, 0,0,xLen-1, yLen-1, 0, zLen-1))

# crop both channels at z=12
z12=ops.image().crop(img, Intervals.createMinMax(0,0,0,12, xLen-1, yLen-1, cLen-1, 12))

# crop channel 0 at z=12
c0z12=ops.image().crop(img, Intervals.createMinMax(0,0,0,12, xLen-1, yLen-1, 0, 12))

# crop an roi at channel 0, z=12
roiC0z12=ops.image().crop(img, Intervals.createMinMax(150,150,0,12, 200, 200, 0, 12))

# display all the cropped images
ui.show("C0", c0)
ui.show("z12", z12)
ui.show("C0z12", c0z12)
ui.show("roiC0z12", roiC0z12)
					

The first few lines specify the parameters and import useful utilities


# @OpService ops
# @Dataset data
# @UIService ui

from net.imglib2.util import Intervals
from net.imagej.axis import Axes
						
  • The op service
  • The UI service
  • The input dataset

Loop through the dimensions and print the axis type and length of each dimension


for d in range(data.numDimensions()):
        print "axis d: type: "+str(data.axis(d).type())+" length: "+str(data.dimension(d))

img=data.getImgPlus()

xLen = data.dimension(data.dimensionIndex(Axes.X))
yLen = data.dimension(data.dimensionIndex(Axes.Y))
zLen = data.dimension(data.dimensionIndex(Axes.Z))
cLen = data.dimension(data.dimensionIndex(Axes.CHANNEL))
						

Use ops to crop various intervals.

  • The Intervals.createMinMax function creates an interval defined by a start and end point

# crop a channel
c0=ops.image().crop(img, Intervals.createMinMax(0, 0, 0,0,xLen-1, yLen-1, 0, zLen-1))

# crop both channels at z=12
z12=ops.image().crop(img, Intervals.createMinMax(0,0,0,12, xLen-1, yLen-1, cLen-1, 12))

# crop channel 0 at z=12
c0z12=ops.image().crop(img, Intervals.createMinMax(0,0,0,12, xLen-1, yLen-1, 0, 12))

# crop an roi at channel 0, z=12
roiC0z12=ops.image().crop(img, Intervals.createMinMax(150,150,0,12, 200, 200, 0, 12))
						

Use the ui service to show our cropped images


ui.show("C0", c0)
ui.show("z12", z12)
ui.show("C0z12", c0z12)
ui.show("roiC0z12", roiC0z12)
					
  • Exercise
    • Crop slice 15 at channel 2

Ops Segment - IJ1 Analyze

  • Use ops to segment an image
    • Create a LOG (Laplacian of Gaussian) kernel
    • Convolve with the LOG kernel
    • Threshold
  • Analyze using ImageJ-1
    • Convert to IJ1 ImagePlus
    • Convert to mask
    • Run analyze particles
  • Use the C0Z16 image generated in "Crop Confocal Series" tutorial
  • In Script Editor, select Templates ▶ Tutorials ▶ Ops Threshold IJ1 Analyze

You should see:


# @OpService ops
# @UIService ui
# @Dataset inputData
# @Double sigma

from net.imglib2.img.display.imagej import ImageJFunctions
from ij import IJ
from java.lang import Integer

# create a log kernel
logKernel=ops.create().kernelLog(Integer(inputData.numDimensions()), sigma);

# convolve with log kernel
logFiltered=ops.filter().convolve(inputData, logKernel)

# display log filter result
ui.show("log", logFiltered)

# otsu threshold and display
thresholded = ops.threshold().otsu(logFiltered)
ui.show("thresholded", thresholded)

# convert to imagej1 imageplus so we can run analyze particles
impThresholded=ImageJFunctions.wrap(thresholded, "wrapped")

# convert to mask and analyze particles
IJ.run(impThresholded, "Convert to Mask", "")
IJ.run(impThresholded, "Analyze Particles...", "display add")
				

The parameters


# @OpService ops
# @UIService ui
# @Dataset inputData
# @Double sigma

					

Import the ImageJFunctions and IJ classes


from net.imglib2.img.display.imagej import ImageJFunctions
from ij import IJ
from java.lang import Integer

					
  • ImageJFunctions: contains utilities used to convert between IJ2 and IJ1 data structures
  • IJ: the classic IJ1 main namespace

Use ops to implement a segmentation pipeline.


# create a log kernel
logKernel=ops.create().kernelLog(Integer(inputData.numDimensions()), sigma);

# convolve with log kernel
logFiltered=ops.filter().convolve(inputData, logKernel)

# display log filter result
ui.show("log", logFiltered)

# otsu threshold and display
thresholded = ops.threshold().otsu(logFiltered)
ui.show("thresholded", thresholded)

				
  • Create Laplacian of Gaussian Kernel
    • Laplacian is a high frequency edge detector
    • Can be combined with Gaussian to deal with noise
  • Convolve LOG kernel with image
  • Apply automatic threshold

Use IJ1 functions to analyze


# convert to imagej1 imageplus so we can run analyze particles
impThresholded=ImageJFunctions.wrap(thresholded, "wrapped")

# convert to mask and analyze particles
IJ.run(impThresholded, "Convert to Mask", "")
IJ.run(impThresholded, "Analyze Particles...", "display add")
						
  • Exercises
    • Make sigma a parameter
    • Use different threshold methods
    • Add deconvolution

Add the option to use deconvolution as an additional preprocessing step


# @OpService ops
# @UIService ui
# @Dataset inputData
# @Boolean deconvolve


from net.imglib2.img.display.imagej import ImageJFunctions
from ij import IJ

if (deconvolve):
	gKernel=ops.create().kernelGauss(inputData.numDimensions(), 3.0)
	inputData=ops.deconvolve().richardsonLucy(inputData, gKernel, 10)
	ui.show("deconvolved", inputData)

						

Threshold Hyperslice

  • Apply Threshold to N-Dimensional Data
    • Specify dimension(s) to process
    • Specify dimension(s) to iterate
  • In ImageJ, select File ▶ Open Samples ▶ Confocal Series (2.2MB)
  • In Script Editor, select Templates ▶ Tutorials ▶ Slicewise Threshold

You should see:


# @OpService ops
# @Dataset data
# @UIService ui

from net.imagej.ops import Ops
from net.imagej import ImgPlus
from net.imglib2 import FinalDimensions
from net.imglib2.type.logic import BitType
from net.imagej.axis import Axes

# first take a look at the size and type of each dimension
for d in range(data.numDimensions()):
        print "axis d: type: "+str(data.axis(d).type())+" length: "+str(data.dimension(d))

xDim = data.dimensionIndex(Axes.X)
yDim = data.dimensionIndex(Axes.Y)
zDim = data.dimensionIndex(Axes.Z)
cDim = data.dimensionIndex(Axes.CHANNEL)

# create the otsu op
otsu=ops.op(Ops.Threshold.Otsu, data)

# create memory for the thresholded image
thresholded=ops.create().img(data.getImgPlus(), BitType())

# call slice wise thresholde axis to process, in this case [0,1] means process the 
# first two axes (x and y)
ops.slicewise(thresholded, data.getImgPlus(), otsu, [xDim,yDim])

# try again with [xDim, yDim, zDim] is the result different??  Why??

# create an ImgPlus using the thresholded img, copy meta data from the input
thresholdedPlus=ImgPlus(thresholded, data.getImgPlus(), True)

ui.show("thresholded", thresholdedPlus)
						

The first half of the script sets up the parameters, imports classes, and prints out the size and type of the input axes:


# @OpService ops
# @Dataset data
# @UIService ui

from net.imagej.ops import Ops
from net.imagej import ImgPlus
from net.imglib2 import FinalDimensions
from net.imglib2.type.logic import BitType
from net.imagej.axis import Axes
	
# first take a look at the size and type of each dimension
for d in range(data.numDimensions()):
        print "axis d: type: "+str(data.axis(d).type())+" length: "+str(data.dimension(d))

xDim = data.dimensionIndex(Axes.X)
yDim = data.dimensionIndex(Axes.Y)
zDim = data.dimensionIndex(Axes.Z)
cDim = data.dimensionIndex(Axes.CHANNEL)
				

Create an Otsu (threshold) op and memory for the result


# create the otsu op
otsu=ops.op(Ops.Threshold.Otsu, data)

# create memory for the thresholded image
thresholded=ops.create().img(data.getImgPlus(), BitType())
						

Then call the slicewise op passing, the output, the input, the threshold op and the axes to process.


ops.slicewise(thresholded, data.getImgPlus(), otsu, [xDim,yDim])

# try again with [xDim, yDim, zDim] is the result different??  Why??

# create an ImgPlus using the thresholded img, copy meta data from the input
thresholdedPlus=ImgPlus(thresholded, data.getImgPlus(), True)

ui.show("thresholded", thresholdedPlus)
						
  • Exercise
    • Pass [xDim, yDim, zDim] as the axis to process. Is the result different? Why?

ops.slicewise(thresholded, data.getImgPlus(), otsu, [xDim,yDim,zDim])
						

Projections

  • Use OpService to create sum and max ops
  • Pass sum and max ops to projection op
  • Use the C0 image generated in the "Crop Confocal Series" tutorial
  • In Script Editor, select Templates ▶ Tutorials ▶ Projections

You should see:


# @OpService ops
# @UIService ui
# @Dataset data

from net.imagej.ops import Ops
from net.imagej.axis import Axes

# get the dimension to project
pDim = data.dimensionIndex(Axes.Z)

# generate the projected dimension
projectedDimensions=[data.dimension(d) for d in range(0, data.numDimensions()) if d!=pDim]
print projectedDimensions

# create memory for projections
maxProjection=ops.create().img(projectedDimensions)
sumProjection=ops.create().img(projectedDimensions)

# use op service to get the max op
maxOp = ops.op(Ops.Stats.Max, data)
# use op service to get the sum op
sumOp = ops.op(Ops.Stats.Sum, sumProjection.firstElement(), data)

# call the project op passing
# maxProjection: img to put projection in
# image: img to project
# op: the op used to generate the projection (in this case "max")
# dimensionToProject: the dimension to project
ops.image().project(maxProjection, data, maxOp, pDim)

# project again this time use sum projection
ops.image().project(sumProjection, data, sumOp, pDim)

# display the results
ui.show("max projection", maxProjection)
ui.show("sum projection", sumProjection)
					

Set up the Parameters and import classe


# @OpService ops
# @UIService ui
# @Dataset data

from net.imagej.ops import Ops
from net.imagej.axis import Axes
						

Calculate projected dimensions and generate memory


# get the dimension to project
pDim = data.dimensionIndex(Axes.Z)

# generate the projected dimension
projectedDimensions=[data.dimension(d) for d in range(0, data.numDimensions()) if d!=pDim]
print projectedDimensions

# create memory for projections
maxProjection=ops.create().img(projectedDimensions)
sumProjection=ops.create().img(projectedDimensions)
						

Create max and sum ops


# use op service to get the max op
maxOp = ops.op(Ops.Stats.Max, data)
# use op service to get the sum op
sumOp = ops.op(Ops.Stats.Sum, sumProjection.firstElement(), data)
						

Call projection op(s) and display outputs


# call the project op passing
# maxProjection: img to put projection in
# image: img to project
# op: the op used to generate the projection (in this case "max")
# dimensionToProject: the dimension to project
ops.image().project(maxProjection, data, maxOp, pDim)

# project again this time use sum projection
ops.image().project(sumProjection, data, sumOp, pDim)

# display the results
ui.show("max projection", maxProjection)
ui.show("sum projection", sumProjection)
						
  • Exercise
    • Try projecting in a different dimension (x or y)
    • Try making the projection dimension a parameter

FFT template matching with Ops + ImgLib2

  • Use C0Z12 and roiC0Z12 from the "Crop Confocal Series" tutorial
  • In Script Editor, select Templates ▶ Tutorials ▶ Find Template

You should see:


# @OpService ops
# @UIService ui
# @Dataset image
# @Dataset template

'''
This example is an 'ops' version of:

http://fiji.sc/ImgLib2_Examples#Example_6c_-_Complex_numbers_and_Fourier_transforms

for which the code and images can be found

https://github.com/imglib/imglib2-tutorials
'''

from net.imglib2.img.display.imagej import ImageJFunctions
from net.imglib2.type.numeric.complex import ComplexFloatType
from net.imglib2.outofbounds import OutOfBoundsMirrorExpWindowingFactory

from net.imglib2.converter import ComplexImaginaryFloatConverter
from net.imglib2.converter import ComplexPhaseFloatConverter
from net.imglib2.converter import ComplexRealFloatConverter

# perform fft of the template

# basic fft call with no parameters
#templateFFT=ops.filter().fft(template.getImgPlus())

# alternatively to pass an outofbounds factory we have to pass every parameter.  We want:
# output='None', input=template, borderSize=10 by 10, fast='True', outOfBoundsFactor=OutOfBoundsMirrorExpWindowingFactory
templateFFT=ops.filter().fft(None, template.getImgPlus(), [10, 10], True, OutOfBoundsMirrorExpWindowingFactory(0.25))

# display fft (by default in generalized log power spectrum)
ImageJFunctions.show(templateFFT).setTitle("fft power spectrum")

# display fft phase spectrum
ImageJFunctions.show( templateFFT,ComplexPhaseFloatConverter() ).setTitle( "fft phase spectrum" )

# display fft real values
ImageJFunctions.show( templateFFT,ComplexRealFloatConverter() ).setTitle( "fft real values" )
        
# display fft imaginary values
ImageJFunctions.show( templateFFT, ComplexImaginaryFloatConverter() ).setTitle( "fft imaginary values" )

# complex invert the fft of the template
c = ComplexFloatType()
for  t in templateFFT:
	c.set(t)
	t.complexConjugate()
	c.mul(t)
	t.div(c)

# create Img memory for inverse FFT and compute inverse 
templateInverse=ops.create().img([template.dimension(0), template.dimension(1)])

ops.filter().ifft(templateInverse, templateFFT)
ui.show("template inverse", templateInverse)

# convolve templateInverse with image
final=ops.filter().convolve(image, templateInverse)
ui.show("final", final)
						

Set up the parameters and import several classes from imglib2


# @OpService ops
# @UIService ui
# @Dataset image
# @Dataset template

from net.imglib2.img.display.imagej import ImageJFunctions
from net.imglib2.type.numeric.complex import ComplexFloatType
from net.imglib2.outofbounds import OutOfBoundsMirrorExpWindowingFactory

from net.imglib2.converter import ComplexImaginaryFloatConverter
from net.imglib2.converter import ComplexPhaseFloatConverter
from net.imglib2.converter import ComplexRealFloatConverter
						

Perform the FFT of the template using Ops


# perform fft of the template

# basic fft call with no parameters
#templateFFT=ops.filter().fft(template.getImgPlus())

# alternatively to pass an outofbounds factory we have to pass every parameter.  We want:
# output='None', input=template, borderSize=10 by 10, fast='True', outOfBoundsFactor=OutOfBoundsMirrorExpWindowingFactory
templateFFT=ops.filter().fft(None, template.getImgPlus(), [10, 10], True, OutOfBoundsMirrorExpWindowingFactory(0.25))
					

Use utilities from imglib2 to display the power spectrum, phase, real and imaginary values of the FFT


# display fft (by default in generalized log power spectrum)
ImageJFunctions.show(templateFFT).setTitle("fft power spectrum")

# display fft phase spectrum
ImageJFunctions.show( templateFFT,ComplexPhaseFloatConverter() ).setTitle( "fft phase spectrum" )

# display fft real values
ImageJFunctions.show( templateFFT,ComplexRealFloatConverter() ).setTitle( "fft real values" )
        
# display fft imaginary values
ImageJFunctions.show( templateFFT, ComplexImaginaryFloatConverter() ).setTitle( "fft imaginary values" )
						

Use imglib2 to calculate the inverse of the template fft:


# complex invert the fft of the template
c = ComplexFloatType()
for  t in templateFFT:
	c.set(t)
	t.complexConjugate()
	c.mul(t)
	t.div(c)
						

Calculate the inverse of the template, convolve with the image and display the result


# create Img memory for inverse FFT and compute inverse 
templateInverse=ops.create().img([template.dimension(0), template.dimension(1)])

ops.filter().ifft(templateInverse, templateFFT)
ui.show("template inverse", templateInverse)

# convolve templateInverse with image
final=ops.filter().convolve(image, templateInverse)
ui.show("final", final)
							

Acknowledgments

SciJava principal investigators:


Kevin
Eliceiri

Jason
Swedlow

Pavel
Tomancak

Anne
Carpenter

Michael
Berthold

SciJava developers:


Barry
DeZonia

Christian
Dietz

Mark
Hiner

Lee
Kamentsky

Brian
Northan

Tobias
Pietzsch

Stephan
Preibisch

Curtis
Rueden

Stephan
Saalfeld

Johannes
Schindelin

And everyone supporting open science and open software!