The vibrant plugin architecture of ImageJ and Fiji ¶
ImageJ and Fiji (a batteries included ImageJ distribution) are amazing pieces of free software originally developed at the National Institutes of Health by Wayne Rasband (who still maintains the original ImageJ software), with newer versions currently developed by Curtis Reuden et al. Here are some papers about the ImageJ ecosystem:
- "ImageJ2: ImageJ for the next generation of scientific image data"
- "Fiji: an open-source platform for biological-image analysis"
- More citations listed: https://imagej.net/Citing
This powerful and vibrant ecosystem is build around the Java Virtual Machine (JVM) to allow for fast, cross-platform scientific image analysis. One of the best parts of this software is the extensibility. ImageJ plugins allow users to extend ImageJ functionality and ImageJ provides the means to easily share those changes with others.
I want to develop ImageJ plugins using Clojure, and compile them to native Java bytecode. In this work in progress I will document how I set up my environment and any issues I had while developing and compiling a plugin.
I'll refer to ImageJ and Fiji interchangeably in this document, but note that I am using Fiji Version 2.0.0-rc-69/1.52i
on Mac OSX. I'd recommend using Fiji if you are following along.
Read this before getting started ¶
Before looking at this, you should really check out Albert Cardona's (lab website, HHMI page, website) excellent guide to working in ImageJ with Clojure https://imagej.net/Clojure_Scripting (he also has great ImageJ Jython tutorials). He put a lot of work into that guide and it is comprehensive and has been extremely helpful for me as I continue to learn Clojure and Java. This is meant to supplement Albert's guide and to show you explicitly how to develop a compiled plugin for ImageJ in Clojure.
Setting up the REPL and development environment ¶
This section will be specific for development in Emacs, but I am sure it won't be difficult to set up in any other editor or IDE that supports Leiningen (a build tool for Clojure) integration.
Dependencies and FunImageJ (Lisp for ImageJ) ¶
I write Clojure in Emacs using Cider, and that is how I want to develop plugins. After some fiddling, I realized that setting up the environment to develop from Emacs with Cider was super easy! ImageJ (and SciJava) are available from Maven, so to work with them you should just have to add them to your project.clj
in your Leiningen project.
First, I'll setup a Leiningen project. (Leiningen is a build tool for Clojure)
lein new imagej_plugin
This gives the following directory structure:
imagej_plugin ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc │ └── intro.md ├── project.clj ├── resources ├── src │ └── imagej_plugin │ ├── core.clj │ └── plugins.config ├── target └── test └── imagej_plugin └── core_test.clj
Now there are a lot of dependencies I may need to work with ImageJ, including SciJava, ImageLib2, and many others. I will add just the basic ImageJ ones at this point, along with a really cool project called FunImageJ, which provides a Lisp (Clojure) interface for image analysis in ImageJ. Kyle Harrington, Curtis Rueden, and Kevin W. Eliceiri wrote a paper about FunImageJ https://doi.org/10.1093/bioinformatics/btx710.
I am new to the Java ecosystem and build tools, so I am still not sure how the different maven repositories and what not work. Everything I have needed so far has been in Maven central (default with Leiningen), which does not contain the SciJava and ImageJ repositories. I found ImageJ here, but lein deps
failed to find it. It turned out I needed to add an external repository to my project.clj
first. Luckily, the FunImageJ repository gave me a hint and I added the following to get started (yours wont look exactly like this, make sure to change the sections appropriately to look like mine!):
(defproject imagej_plugin "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:repositories [["imagej-releases" "https://maven.imagej.net/content/repositories/releases/"]
["imagej-snapshots" "https://maven.imagej.net/content/repositories/snapshots/"]]
:dependencies [[org.clojure/clojure "1.8.0"]
[fun.imagej/fun.imagej "0.2.1"]
[net.imagej/ij "1.52g"] ;; main imagej
[net.imagej/imagej-common "0.26.0"]] ;; not sure if I need it.
:main imagej-plugin.The_Test
:aot [imagej-plugin.The_Test]
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
Alternatively, you can use only the stuff in the default Maven with these two artifacts
;; ...
[org.scijava/scijava-common "2.75.1"] ;; for scijava
[gov.nih.imagej/imagej "1.45"] ;; for imagej
;; ...
Now, lets download the dependencies with lein deps
from the imagej_plugin
directory.
# bash
lein --version
# > Leiningen 2.7.1 on Java 1.8.0_144 Java HotSpot(TM) 64-Bit Server VM
lein deps
# downloads dependencies
NOTE! I had to use Leiningen version 2.7.1, unfortunately using the latest leiningen version (at this time 2.8.3) I had errors because one of the artifacts from ImageJ uses insecure HTTP, and I don't know which one does it. Either way, I am not able to make this work with the new Leiningen version, so even though it is not safe to use HTTP, in the interest of getting this working, I'll have to use this for now and fix it when I can.
Now you can test your environment using this code from the ImageJ Clojure scripting guide:
(rename core.clj
to The_Test.clj
)
;; The_Test.clj
(ns imagej_plugin.The_Test
:gen-class)
(import '(ij IJ))
(def gold (IJ/openImage "https://imagej.net/images/AuPbSn40.jpg"))
(.show gold)
and then when you cider-jack-in
and cider-eval-file
, the image should pop right up (if you are connected to the internet)!
Cool initial testing! It looks relatively trivial to develop ImageJ plugins with Clojure from Emacs or any other text editor. Now, let's see how we would compile this into a regular plugin.
Clojure plugins as ImageJ scripts ¶
Your Clojure plugin will appear in the Plugins menu simply by moving your The_Test.clj
to the Plugins
directory in your ImageJ app folder (on my Mac this is /Applications/Fiji.app/Plugins/
). This is nice, but the file The_Test.clj
does not appear to be converted to Java bytecode (a .class
file), so it is likely being interpreted as a script and will not be as fast as native Java bytecode. I'd like to compile the Clojure source code into native Java bytecode packaged into a Jar
for speed and simplicity, and to take advantage of the full speed and power of the JVM.
For this, we need to take a few more steps.
The ImageJ Jar
plugin architecture
¶
This link on the ImageJ website describes how ImageJ searches for plugins and creates a menu list for them. When it finds a .jar
, it searches for classes which have underscores in their names to create the menu options. Alternatively, it looks for a file called plugins.config
. plugins.config
tells ImageJ not to search the Jar
, and instead to create the menu using the text within the file. The example on the ImageJ website looks like so (copied from the imagej website):
# This is a comment (empty lines are also ignored) # This line will add "Blob" to the "New" submenu of the "File" menu. # Clicking on "Blob" will call the plugin class "my.test.Test" File>New, "Blob", my.test.Test
In our case, let's start by changing The_Test.clj
to a simple test file:
;; src/imagej_plugin/The_Test.clj
(ns imagej-plugin.The_Test
(:gen-class))
(import '(ij IJ))
(defn main
[]
(ij.IJ/log "THIS IS OUR TEST APP\n If you see this, we did it correctly!"))
(main)
And alongside it, in the src
directory, let's add the plugins.config
file as well:
# config file testing # This line will add "Test me" to the "Plugins" menu # Clicking on "Test me" will call the plugin class "imagej_plugin.The_Test" Plugins, "Test me", imagej_plugin.The_Test
and now we should be all set up for our menu structure.
Compiling and testing in ImageJ ¶
Great, now from a shell, let's compile this to a Jar
:
(again from the imagej_plugin
directory)
# bash
# from the imagej_plugin directory
lein jar
The output should look like this
Compiling imagej-plugin.The_Test THIS IS OUR TEST APP If you see this, we did it correctly! Compiling imagej-plugin.The_Test THIS IS OUR TEST APP If you see this, we did it correctly! Created /Users/Nick/personal_projects/imagej_plugin/target/imagej_plugin-0.1.0-SNAPSHOT.jar
Now, we need to copy this to our Fiji.app
Plugins directory.
Also, notice how we referred to this Jar
in our plugins.config
as imagej_plugin.The_Test
, not as imagej_plugin-0.1.0-SNAPSHOT.The_Test
, which is what Leiningen automatically names the compiled Jar
. For this to work, we need to rename the Jar
in the Plugins folder from imagej_plugin-0.1.0-SNAPSHOT.jar
to imagej_plugin.jar
.
Update!
You can tell lein what to name your Jar (or Uberjar) with the following in your project.clj
;; project.clj
...
:jar-name "The_Test.jar"
:uberjar-name "The_Test.jar"
...
Now the compiled jar will be named appropriately for ImageJ.
Let's restart ImageJ and see how it looks.
You can see the name we assigned our plugin appears in the plugins menu!
Now, when we run it, you should see the following:
Here, we just developed a simple ImageJ test plugin in Emacs with Clojure, compiled it to a Jar
and installed it in ImageJ!
Next time, we will add a graphical user interface and some actual functionality to our plugin.