Edit page History

JRuby Scripting

    JRuby is a marvellous project that created a complete implementation of Ruby that runs in the JVM. The excellent work of the authors of JRuby has made it very simple for us to add JRuby scripting into ImageJ.

    JRuby scripting in ImageJ is a nice alternative to scripting using ImageJ’s macro language. It has the following advantages:

    • You don’t have to learn a new language to script ImageJ (assuming you know Ruby)
    • You’re not limited to using the functionality exposed by the macro language: you can use any class in ImageJ, one of its plugins or standard Java classes
    • Developing JRuby scripts is very fast compared to developing plugins in Java

    (These advantages, of course, are shared by the Jython Scripting, Clojure Scripting, Beanscript and Javascript Scripting bundled in Fiji.)

    If you have any questions or suggestions about JRuby scripting in ImageJ, please contact the ImageJ forum. Have fun!

    Tutorial

    Let’s start writing some JRuby right away - start up the interpreter by going to Plugins › Scripting › JRuby Interpreter . The interpreter window will pop up, but it may take a little time for the JRuby runtime to be ready. You should initially see the message:

     Starting JRuby ...

    … and when it’s ready, the line:

     Done.

    Now you can start typing Ruby expressions into that window, such as:

     >>> "hello there".upcase[1..4]
     ELLO

    It would be a good idea to take a quick look at the page on Scripting Help for tips on using this interpreter window.l

    Try loading one of the ImageJ sample images by going to File › Open Samples › T1 Head (2.4M, 16-bits) . Once you’ve done that we’ll examine the image using JRuby. You can get a reference to the current image with ij.IJ.getImage. Try assigning the result to a variable, like this:

     >>> i = ij.IJ.getImage
     imp[t1-head.tif 256x256x129]

    Now we can find out the width and the depth of the stack like this:

     >>> w = i.getWidth
     256
     >>> d = i.getStackSize
     129

    Many of the operations that you might want to perform on the image are available via the ImageProcessor associate with a slice. For example, to invert the current slice do:

     i.getProcessor.invert

    That won’t actually produce a visible effect on the image until you also call i.updateAndDraw on the image:

     i.updateAndDraw

    If you scroll through the stack now you should find that one of the slices is inverted.

    Note On Names

    You might find the use of “ij.IJ.getImage” above slightly suspicious if you’re used to Java and the ImageJ API. “ij” is the Java package name, “IJ” is the class name in that package and “getImage” is a static method in that class which returns a reference to an ImagePlus object representing the current image or null if there is none. How does this work? We have set up the JRuby interpreter in ImageJ so that if a method or class name can’t be found it will look for a correponding class or method name in the ij.* packages. JRuby by default does exactly this for the java.* classes, so for example the following works without having to explicitly include the java.util.Random class:

     >>> rng = java.util.Random.new
     java.util.Random@bf9a12
     >>> rng.nextInt 1024
     603
     >>> rng.nextInt 1024
     94

    Note that this is an example of creating an object in JRuby; you use the usual Something.new syntax of Ruby, but it creates an object of the standard Java class. Hopefully you’re thinking “Excellent!” or something similar at this stage…

    Importing classes

    If you need to use classes that aren’t in the java.* or ij.* hierarchy—or if you are developing JRuby scripts in the Script Editor—you will have to include them explicitly. To reference Java classes from JRuby you will need to import them.

    Unlike ImageJ 1.x, ImageJ2 (and therefore Fiji) does not automatically import any classes. Consequently, scripts written for ImageJ 1.x will not run in ImageJ2 without adding the proper imports. The rationale is that the auto-import feature is not safe. What if two classes of the same name live in two different packages? Or if a new class is introduced that makes formerly unique names ambiguous? All of a sudden, all of the scripts that reference the original class no longer work. In short: auto-imports are dangerously imprecise.


    For example, in the classpath of Fiji there is a useful class called util.BatchOpener, that has static methods for opening files as arrays of ImagePlus objects (one per channel) without showing them. To use these methods, you would have to do:

     >>> java_import 'util.BatchOpener'
     util.BatchOpener

    Then you can, for example, do the following:

     >>> a = BatchOpener.open "/home/mark/confocal/test.lsm"
     [Lij.ImagePlus;@b1406b
     >>> a.length
     2
     >>> a[0].getTitle
     C1-test.lsm
     >>> a[1].getTitle
     C2-test.lsm
     >>> a[1].show

    Example: Generating a Plasma Cloud

    In this first example, we’ll just create a small script to create a fractal “plasma cloud” image. We’ll start of experimenting in the interpreter to make sure we’ve got the code to create images correctly. To create the RGB image in the first place, we create a ColorProcessor and then an ImagePlus from that:

    >>> w = 800
    800
    >>> h = 600
    600
    >>> cp = ij.process.ColorProcessor.new(w,h)
    ip[width=800, height=600, min=0.0, max=255.0]
    >>> i = ij.ImagePlus.new "Plasma Cloud", cp
    imp[Plasma Cloud 800x600x1]
    

    … and now show it:

    >>> i.show
    

    We’ll manipulate the pixel array directly, so it’s worth checking that this will work OK. Here we try to set half of the image to green:

    >>> i
    imp[Plasma Cloud 800x600x1]
    >>> pixels = cp.getPixels
    [I@e038c4
    >>> 0.upto( w * (h / 2) - 1) { |j| pixels[j] = 0x0000FF00 }
    0
    >>> i.updateAndDraw
    

    Hopefully that worked OK. The color value specified there is in ARGB format. You may have noticed that this step was rather slow - there is quite a bit of magic involved in JRuby’s transparent access between Ruby and Java, which has an inevitable performance cost. But if your goal is not so much the speed with which the script is executed, but to get a working script as fast as possible, JRuby scripting may well be your optimal tool.

    (By the way, sometimes it’s even possible to use java’s optimizations in ruby. Replace the

    0.upto( w * (h / 2) - 1) { |j| pixels[j] = 0x0000FF00 }
    

    with

     java.util.Arrays.fill(pixels, 0, w*h/2 - 1, 0x0000FF00)
    

    and get a free 240 times speedup.)

    You would probably proceed at this stage by switching to a text editor and creating a script with the extension “.rb” and underscores in the name in the Fiji plugins directory. You should find an example “Plasma_Cloud.rb” script in plugins/Examples which is based on the basics we worked out above. The output of this script looks like this:

    Example "Plasma Cloud" image

    Example: Batch Converting File Formats

    This is a short example script showing how to convert a directory of LSM files into BioRad .PIC format using JRuby and the util.BatchOpener methods that I mentioned above. This just hard-codes the paths in the filesystem, so you would need to edit it if you want to do something similar. Nonetheless, hopefully this is instructive: filtering filenames, and so on, is typically much more convenient using JRuby than the ImageJ macro language:

    input_directory  = "/home/mark/lsm-examples/"
    output_directory = "/home/mark/lsm-examples/biorad/"
    
    include_class 'util.BatchOpener'
    include_class 'Biorad_Writer'
    
    # Check that the input directory exists:
    unless FileTest.directory? input_directory
      ij.IJ.error "Input directory '#{input_directory} was not found"
      exit(-1)
    end
    
    # Create the output directory if it doesn't exist:
    unless FileTest.exist? output_directory
      Dir.mkdir output_directory
    end
    
    # Biorad filenames have a standard format, which we generate with
    # this function.  ('channel' should be 1-indexed):
    def make_biorad_filename(lsm_filename,channel)
      lsm_filename.gsub(/.lsm$/,sprintf("%02d.pic",channel))
    end
    
    Dir.entries(input_directory).each do |e|
      # Skip anything that doesn't have a '.lsm' extension:
      next unless e =~ /\.lsm$/
      puts "Converting: #{e}"
      # Open the image file to an array of ImagePlus objects:
      a = BatchOpener.open("#{input_directory}#{e}")
      # Create the writer plugin object:
      writer = Biorad_Writer.new
      # Now, for each channel in the input image, write out
      index = 0
      a.each do |image|
        # The Biorad_Writer doesn't like COLOR_256 images, so convert to
        # GRAY8:
        ij.process.StackConverter.new(image).convertToGray8
        biorad_filename = make_biorad_filename e, index + 1
        puts "    Writing: #{biorad_filename}"
        writer.save image, output_directory, biorad_filename
        index += 1
        image.close
      end
    end
      
    ij.IJ.showMessage("Finished converting to Biorad format!")
    

    Converting ImageJ Macros to JRuby

    To help with porting existing ImageJ macros to JRuby, I have made a start on implementing similarly named JRuby functions to the standard ImageJ macros. These have been defined with idiomatic Ruby function names as well as the “lower camel case” style ImageJ macro names, so instead of “image = ij.IJ.getImage” as used above, you can use either:

     image = getImage

    or:

     image = get_image

    The “run” method may also be particularly useful for calling existing ImageJ plugins and commands. The next section has an example of the use of this. It may be instructive to compare the “Blobs Demo” macro from the ImageJ distribution with a version ported to JRuby . The use of the analagous function in JRuby is not always the same - for example, if you compare the invocation of getSelectionCoordinates, you’ll find that whereas the ImageJ macro version passes in the output variables:

     getSelectionCoordinates(xCoordinates, yCoordinates);

    … the JRuby version can return two arrays:

     x_coordinates, y_coordinates = get_selection_coordinates

    A note for the interested programmer: About 15% of the macro functions have be done so far, and if anyone wanted to help out with doing the rest, that would be excellent! The source code can be found here .

    Example: Generating Red/Cyan Anaglyphs

    This example script can be found in the Plugins/Examples/ folder of Fiji. It will take an image stack and generate from it an image that should appear in 3D when viewed through red and cyan glasses. All that this does is to do two maximum intensity projections from two slightly different angles and merges them together. It’s a nice example because all the work is done by existing ImageJ commands. If you look at the script, you’ll see that the first step is to run the “3D Project…” plugin with some slightly convoluted options:

    projection_options = "projection=[Brightest Point] axis=Y-Axis "
    projection_options += "initial=-#{degree_separation / 2} "
    projection_options += "total=#{degree_separation} "
    projection_options += "rotation=#{degree_separation} "
    projection_options += "interpolate"
    
    run "3D Project...", projection_options
    

    In general, the best way to figure out what these options should be is to start the macro recorder with “Plugins › Macros › Record… “ and run the plugin. In this case, the output in the macro recorder looks like this:

     run("3D Project...", "projection=[Brightest Point] axis=Y-Axis slice=1.20 initial=-2 total=4 
                   rotation=4 lower=1 upper=255 opacity=0 surface=100 interior=50 interpolate");

    Hopefully it should be obvious how I derived the ‘projection_options’ string in the script from that output. The next part of the script splits the hyperstack generated by that command and merges it with appropriate colours. Similarly this can be figured out with the help of the macro recorder, the only tricky bit being that one has to predict the names of the images that are output from each step. There are further helpful comments in the script itself.

    Some example output:

    Example anaglyph image

    Script Parameters

    When using Script Parameters, e.g., in the Script Editor, you need to use a $ before @ variables, due to a limitation in the scoping, as in this example from Script Templates:

    Greeting.rb

    Library

    There is a library called imagej.rb for convenience. It contains a number of useful functions related to ImageJ. It is loaded by default when creating a new JRuby script in the Script Editor.

    What next?

    You may want to first have a quick look at the Scripting Help page for generic instructions in using the interpreter and script interfaces, and the Scripting comparisons page for an example written in several of the different scripting languages available. The JRuby example shows how to implement a Java interface in JRuby.

    JRuby offers some more nice features, have a look at them in the JRuby documentation: https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby

    And of course, if you want to know how to use a class from ImageJ or any of its projects, visit: https://javadoc.imagej.net.