Difference between revisions of "Jython Scripting"
(→Self written Jython packages for ImageJ: Adding content to this section.)
m (→Self written Jython packages for ImageJ: Adding '/' at the end of folder names.)
|Line 346:||Line 346:|
Revision as of 02:29, 29 September 2016
Template:Scripting== Introduction ==
- 1 When to use Jython
- 2 Jython basics for ImageJ
- 3 Self written Jython modules for ImageJ
- 4 Self written Jython packages for ImageJ
- 5 Using maven to build packages
- 6 References
When to use Jython
All scripting language supported by ImageJ can be used to access the ImageJ API. There are only differences in how the imports are handled and in the syntax of the selected language. Jython has a syntax that differs from most other language as indentations instead of brackets are used to group code blocks.
The following list will help you to decide if Jython is the right choice to create scripts for ImageJ:
- If you have experience with Python you can easily use Jython for ImageJ scripting. But you have to keep in mind that tools commonly used in many Python projects (e.g. Numpy) are not available in Jython. By building your own modules you can create complex scripts that otherwise are only possible by writing ImageJ plugins in Java.
- If don't have any experience in programming, the Python language is a good choice to start with. If your only aim is to write scripts for ImageJ, there are other languages you should try first (e.g. Groovy).
- In Python many problems can be solved with less code than in other languages. Still the code is easy to read. Have a look at the examples on this page and decide if you want to start using Python for ImageJ scripting.
The Java implementation of Python is limited in functionality. One can use the standard library, but it's not possible to install additional Python modules. Moreover a growing number of projects build on Python 3 which is not fully compatible with Python 2 Jython is based on. If you want to start learning Python it's recommended to learn Python 3.x instead of Python 2.
Even with the given limitations Jython is a powerful language for ImageJ scripting. Hopefully the examples on this page can convince you of that.
Jython basics for ImageJ
The aim of this page is not to teach how to program in Python. This purpose is much better fulfilled by the documentation of Python 2. The focus of this page is to show how features of the Python language can be useful for ImageJ scripting.
That is why more complex examples are used that are fully functional. Just copy the code to the Script Editor and try them by yourself. Extensive in-line documentation is used to explain the implementation.
Image selection using the GenericDialog class
This example script will create up to 10 new images and create a GenericDialog to select 3 of them. Finally the names of the selected images are printed to the Log window. It is recommended to copy the code to the Script Editor and run it by yourself.
The following list links to documentation of the used Python features:
- Future statement definitions
- Built-in Functions
- List Comprehensions
- Generator Expressions
- ** (double star) and * (star) parameters
- Top-level script environment (__main__)
- Purpose of the single underscore “_” variable
# The module __future__ contains some useful functions: # https://docs.python.org/2/library/__future__.html from __future__ import with_statement, division # This imports the function random from the module random. from random import random # Next we import Java Classes into Jython. # This is how we can acces the ImageJ API: # https://imagej.nih.gov/ij/developer/api/allclasses-noframe.html from ij import IJ, WindowManager from ij.gui import GenericDialog # A function is created with the def keyword. # This function does not need any parameters. def create_test_image(): # Python uses indentation to create code blocks # Local variables are assigned. # We can assign the same value to more than one variable. image_width = image_height = 512 box_width = box_height = 128 offset_x = offset_y = 192 counts = 64 stdv = 16 # The build in function int() is used to convert float to int. # The variable random contains a function that is called by adding parentheses. offset_x = int(2 * random() * offset_x) offset_y = int(2 * random() * offset_y) # We can define a function inside a function. # Outside of create_test_image() this function is not available. # By adding an asterisk to a parameter, all given parameters are combined to a tuple. def make_title(*to_concat): prefix = 'TestImage' # To create a tuple with a single entry the comma is necessary. # The 2 tuples are concatenated by using the + operator. to_join = (prefix,) + to_concat # We create a generator that converts every singe entry of the tuple to a string. strings_to_join = (str(arg) for arg in to_join) # The string ',' has a join method to concatenate values of a tuple with the string as seperator. # The result is a string. return ','.join(strings_to_join) def check_existence(title): if WindowManager.getIDList() is None: return False image_titles = (WindowManager.getImage(id).getTitle() for id in WindowManager.getIDList()) return title in image_titles # To negate an expression put not in front of it. if not check_existence(make_title(offset_x, offset_y)): # The following code has been created by using the Recorder of ImageJ, set to output Java code. # By removing the semicolons, the code can be used in Jython. # The parameters can be modified by using variables and string concatenation. imp = IJ.createImage(make_title(offset_x, offset_y), "8-bit black", image_width, image_height, 1) # The build in function str() is used to convert int to string. IJ.run(imp, "Add...", "value=" + str(counts)) imp.setRoi(offset_x , offset_y, box_width, box_height) IJ.run(imp, "Add...", "value=" + str(counts)) IJ.run(imp, "Select None", "") IJ.run(imp, "Add Specified Noise...", "standard=" + str(stdv)); # We don't want to confirm when closing one of the newly created images. imp.changes = False imp.show() # This function uses parameters. # A default value is given to the third parameter. def create_selection_dialog(image_titles, defaults, title='Select images for processing'): gd = GenericDialog(title) # The build in function enumerate() returns two values: # The index and the value stored in the tuple/list. for index, default in enumerate(defaults): # for each loop we add a new choice element to the dialog. gd.addChoice('Image_'+ str(index + 1), image_titles, image_titles[default]) gd.showDialog() if gd.wasCanceled(): return None # This function returns a list. # _ is used as a placeholder for values we don't use. # The for loop is used to call gd.getNextChoiceIndex() len(defaults) times. return [gd.getNextChoiceIndex() for _ in defaults] # It's best practice to create a function that contains the code that is executed when running the script. # This enables us to stop the script by just calling return. def run_script(): while WindowManager.getImageCount() < 10: create_test_image() image_titles = [WindowManager.getImage(id).getTitle() for id in WindowManager.getIDList()] # range(3) will create the list [0, 1, 2]. selected_indices = create_selection_dialog(image_titles, range(3)) # The script stops if the dialog has ben canceld (None was returned from create_selection_dialog). if selected_indices is None: print('Script was canceld.') return # We have to get the corresponding IMagePlus objects. selected_imps = [WindowManager.getImage(id) for id in [WindowManager.getIDList()[index] for index in selected_indices]] # The previous line can be split into 2 lines: # selected_ids = [WindowManager.getIDList()[index] for index in selected_indices] # selected_imps = [WindowManager.getImage(id) for id in selected_ids] for imp in selected_imps: # Strings can be formated using the % operator: # http://www.learnpython.org/en/String_Formatting IJ.log('The image \'%s\' has been selected.' % imp.getTitle()) # If a Jython script is run, the variable __name__ contains the string '__main__'. # If a script is loaded as module, __name__ has a different value. if __name__ == '__main__': run_script()
Using Scripting Parameters
The second example is inspired by atomic resolution images recorded with an Transmission Electron Microscope (TEM). Such images show a regular structure (a crystal), but the images are noisy because of the low signal. By using a Fourier filter the contrast can be enhanced.
The script will create a periodic structure and add some random noise. The user can control the parameters of the created image. This is realized using Script parameters. The Fourier filtering has been created by using the Recorder. Finally a simple image calculator is used to show that functions can be passed as parameters.
This list links to the documentation of Python features that are introduced with this example:
# @String(value='Please set some parameters.', visibility='MESSAGE') message # @Short(label='Image size', value=512, min=128, max=2048, stepSize=128, style="slider") img_size # @Double(label='Image amplitude', value=100) amplitude # @Short(label='Spacing', value=16, min=8) spacing # The parameters in front of this comment are populated before the script runs. # Details on Script parameters can be found at # http://imagej.net/Script_parameters # The module __future__ contains some useful functions: # https://docs.python.org/2/library/__future__.html from __future__ import with_statement, division # It's best practice to create a function that contains the code that is executed when running the script. # This enables us to stop the script by just calling return. def run_script(): # We can use import inside of code blocks to limit the scope. import math from ij import IJ, ImagePlus from ij.process import FloatProcessor blank = IJ.createImage("Blank", "32-bit black", img_size, img_size, 1) # This create a list of lists. Each inner list represents a line. # pixel_matrix is the first line where y=0. pixel_matrix = split_list(blank.getProcessor().getPixels(), wanted_parts=img_size) # This swaps x and y coordinates. # http://stackoverflow.com/questions/8421337/rotating-a-two-dimensional-array-in-python # As zip() creates tuples, we have to convert each one by using list(). pixel_matrix = [list(x) for x in zip(*pixel_matrix)] for y in range(img_size): for x in range(img_size): # This function oszillates between 0 and 1. # The distance of 2 maxima in a row/column is given by spacing. val = (0.5 * (math.cos(2*math.pi/spacing*x) + math.sin(2*math.pi/spacing*y)))**2 # When assigning, we multiply the value by the amplitude. pixel_matrix[x][y] = amplitude * val # The constructor of FloatProcessor works fine with a 2D Python list. crystal = ImagePlus("Crystal", FloatProcessor(pixel_matrix)) # Crop without selection is used to duplicate an image. crystal_with_noise = crystal.crop() crystal_with_noise.setTitle("Crystal with noise") IJ.run(crystal_with_noise, "Add Specified Noise...", "standard=%d" % int(amplitude/math.sqrt(2))) # As this is a demo, we don't want to be ask to save an image on closing it. # In Python True and False start with capital letters. crystal_with_noise.changes = False crystal.show() crystal_with_noise.show() filtered = fft_filter(crystal_with_noise) # We create a lambda function to be used as a parameter of img_calc(). subtract = lambda values: values - values """ This is a short form for: def subtract(values): return values - values """ # The first time we call img_calc with 2 images. difference = img_calc(subtract, crystal, filtered, title="Difference of 2") difference.show() # The first time we call img_calc with 3 images. minimum = img_calc(min, crystal, filtered, crystal_with_noise, title="Minimum of 3" minimum.show() for imp in (crystal, crystal_with_noise, filtered, difference, minimum): IJ.run(imp, "Measure", "") # Functions can be defined after they are used. # This is only possible if the main code is encapsulated into a function. # The main function has to be called at the end of the script. def img_calc(func, *imps, **kwargs): """Runs the given function on each pixel of the given list of images. An additional parameter, the title of the result, is passed as keyword parameter. We assume that each image has the same size. This is not checked by this function. """ # If the keyword parameter is not passed, it is set to a default value. if not kwargs['title']: kwargs['title'] = "Result" # This is a 2D list: list[number of images][pixels per image] . pixels = [imp.getProcessor().getPixels() for imp in imps] # The function is called pixel by pixel. # zip(*pixels) rotates the 2D list: list[pixels per image][mumber of images]. result = [func(vals) for vals in zip(*pixels)] # result is a 1D list and can be used to create an ImagePlus object. from ij import ImagePlus from ij.process import FloatProcessor return ImagePlus(kwargs['title'], FloatProcessor(img_size, img_size, result)) def split_list(alist, wanted_parts=1): """Split a list to the given number of parts.""" length = len(alist) # alist[a:b:step] is used to get only a subsection of the list 'alist'. # alist[a:b] is the same as [a:b:1]. # '//' is an integer division. # Without 'from __future__ import division' '/' would be an integer division. return [ alist[i*length // wanted_parts: (i+1)*length // wanted_parts] for i in range(wanted_parts) ] def fft_filter(imp): """ Removing noise from an image by using a FFT filter This are operations copied from the ImageJ macro recorder. Jython does not complain when you forget to remove the semicolons. """ from ij import IJ IJ.run(imp, "FFT", ""); # No ImagePlus is returned by the FFT function of ImageJ. # We need to use the WindowManager to select the newly created image. from ij import WindowManager as wm fft = wm.getImage("FFT of " + imp.getTitle()) IJ.run(fft, "Find Maxima...", "noise=64 output=[Point Selection] exclude"); # Enlarging the point selectins from Find Maxima. IJ.run(fft, "Enlarge...", "enlarge=2"); # Inverting the selection. IJ.run(fft, "Make Inverse", ""); IJ.run(fft, "Macro...", "code=v=0"); IJ.run(fft, "Inverse FFT", ""); fft.changes = False fft.close() imp_filtered = wm.getImage("Inverse FFT of " + imp.getTitle()) imp_filtered.setTitle("Filtered " + imp.getTitle()) imp_filtered.changes = False return imp_filtered # If a Jython script is run, the variable __name__ contains the string '__main__'. # If a script is loaded as module, __name__ has a different value. if __name__ == '__main__': run_script()
Self written Jython modules for ImageJ
In Jython you can write all commands line by line in a single file and execute it. To create a neat program, functions and classes can be used to structure code. To prevent using copy&past for regularly used functions and classes, modules are the way to choose. Modules are files that contain functions and classes to import into other files.
To load modules, one has to save them to a directory where Jython will find them. Two lines of code will reveal these directories to you:
from sys import path print(path)
When running this code the result is an output like
['/home/michael/Software/ImageJ.app/jars/Lib', '/home/michael/Software/ImageJ.app/jars/jython-shaded-2.5.3.jar/Lib', '__classpath__', '__pyclasspath__/']
This tells us that the folder
jars/Lib/ inside our ImageJ/Fiji directory is the right place to save modules. As
Lib/ does not exist by default, we have to create it.
When a module is imported for the first time, Jython will compile it to Java code. If there is a module named
myModule.py, Jython will create a file called
myModule$py.class. The next time the module is imported, Jython will use the class file instead of the py file. When modifying the module, it necessary to restart ImageJ/Fiji to use the modified one. A work around is the following code (found at stackoverflow) that will force Jython to recompile all modules:
# Use this to recompile Jython modules to class files. from sys import modules modules.clear() # Imports of Jython modules are placed below: import myModule
Adding a custom directory
If you don't want to use
jars/Lib/ to save your modules, you have to extend the array
from sys import path from java.lang.System import getProperty # extend the search path by $FIJI_ROOT/bin/ # 'fiji.dir' works for plain ImageJ, too. path.append(getProperty('fiji.dir') + '/bin') # an alternative can be the users home directory # path.append(getProperty('user.home') + '/JythonModules') # Now you can import $FIJI_ROOT/bin/myModule.py import myModule
getProperty() accepts many more strings. A list can be found at The Java Tutorials.
Self written Jython packages for ImageJ
On the way to perfectly organize Jython code, packages are the next step. In Jython, folders that contain modules are made packages by adding the file
__init__.py. This file can be empty. An folder structure can look like this:
Imagej.app/jars/lib/ -- myModule.py -- myPackage/ -- __init__.py -- mathTools.py -- customFilters.py -- fftTools.py -- myPackage2/ -- __init__.py -- mathTools.py -- stackProcessing.py
There are two packages and one module. The first package contains three modules and the second package contains two modules. We can import the modules on different ways:
# Import the single module using the default name: import myModule # Import mathTools from the first package import myPackage.mathTools # Use a function from the imported module myPackage.mathTools.aFunction() # Import mathTools from the second package from myPackage2 import mathTools # Use a function from the imported module without prefixing the package mathTools.aFunction() # Import customFilters from the first package and rename it from myPackage import customFilters as filters # Use a function from customFilters.py filters.aFunction() # Importing all module from a package from myPackage2 import * # The next line will fail stackProcessing.aFunction()
The reason for the last import to fail is the empty
__init__.py. We have to define which modules of the package are imported when using
import *. This is done by setting the variable
myPackage2 this line of code is needed:
__all__ = ["mathTools", "stackProcessing"]
Besides setting this variable, the file can contain normal Jython code that is executed on import.
Using maven to build packages
- Wikipedia entry on Jython. Accessed: 2016-08-30