Difference between revisions of "Clojure Scripting"

m (Added category)
Line 197: Line 197:
  
 
* To get the arguments passed to the JVM, see contents of variable *command-line-args*
 
* To get the arguments passed to the JVM, see contents of variable *command-line-args*
 +
 +
[[Category:Scripting]]

Revision as of 16:18, 19 May 2008

Clojure tutorial for ImageJ

Check out clojure web site and particularly the chapter on Java interoperability.

Language basics

A ';' defines the start of a comment, just like '//' does in Java.

A function definition declares parameters within [].

Local variables are declared with let, and global variables with def.

Functions are defined with defn, and are visible globally. Hence a function declared within a let statement has access to variables declared in it. This method enables closures.

Calling methods and variables on a java object

There are two ways, the second syntactic sugar of the first. Below, imp is a pointer to an ImagePlus:

; java-ish way:
(. imp (getProcessor))

; lisp-ish way:
(.getProcessor imp)

To call a method on an object returned by a method call, there is a simplified way:

; double way:
(. (. imp (getProcessor)) (getPixels))

; simplified double way:
(.. imp (getProcessor) (getPixels))

; or lisp-ish way:
(.getPixels (.getProcessor imp))

To call a variable or 'field', just do it like a method call but without the parentheses:

(. imp changes)

(. ij.plugin.filter.PlugInFilter DOES_ALL)

Above, notice how a class name is used instead of a pointer to call static fields.

To enhance readability, use import when appropriate. Imports remain visibile throughout the current namespace:

(import '(java.awt Color Rectangle)
        '(ij.plugin.filter PlugInFilter))

(new Rectangle 0 0 500 500)

(. PlugInFilter DOES_ALL)

Choose whatever matches your mental schemes best.


Defining variables: obtaining the current image

As a local variable imp declared within a let statement:

(let [imp (. ij.WindowManager (getCurrentImage))]
    ; print its name
    (println (. imp (getTitle))))

; Variable imp NOT visible from outside let statement:
(println (. imp (getTitle)))
---> ERROR

As a general variable visible from the entire namespace:

(def *imp* (. ij.WindowManager (getCurrentImage)))

(println (. *imp* (getTitle)))

; Alternative syntax
(println (.getTitle *imp*))

By convention, in lisp global variables are written with asterisks in the name.


A let statement lets you declare any number of paired variable name / values, even referring to each other in sequence:

(let [imp (. ij.WindowManager (getCurrentImage))
      ip (. imp (getProcessor))
      pix (. ip (getPixels))
      pix2 (.. imp (getProcessor) (getPixels))]
    ; do some processing ...
    (println (str "number of pixels: " (count pix))))

Any number of let statements may be nested together:

(let [imp (. ij.WindowManager (getCurrentImage))]
    ; do whatever processing here
    (let [ip (. imp (getProcessor)]
        ; print first pixel
	(println (str (. ip (getPixel 0 0)))))))


Defining a closure

In the following a function is declared within the scope of the local variable rand, which contains an instance of java.util.Random. All calls to the function rand-double will use the same random number generator instance with seed 69997.

The dotimes loop will then print 10 different pseudo-random numbers. If the rand was a new Random with seed 69997 every time, all 10 numbers would be exactly the same.

You can think of a function inside a closure as a static function using a static variable (in Java), but it's more than that, since the function will be able to access parameters on the global namespace and also in any other local namespace in which the let is declared. For example, another let, or even another defn!

(let [rand (new java.util.Random 69997)]
    (defn rand-double []
        (. rand (nextDouble))))

(dotimes i 10
    (println (rand-double)))

Looping an array of pixels

For example, to find the min and max values:

; Obtain the pixels array from the current image
(let [imp (. ij.WindowManager (getCurrentImage))
      pixels (.. imp (getProcessor) (getPixels))
      min (apply min pixels)
      max (apply max pixels)]
    (println (str "min: " min ", max: " max)))

The above code does not explicitly loop the pixels: it simply applies a function to an array.

To loop pixels one by one, use any of the following:

(let [imp (.getCurrentImage ij.WindowManager)
      pixels (.. imp (getProcessor) (getPixels))]

      ; First loop with "dotimes"
      (dotimes i (count pixels)
          (println (aget pixels i)))

      ; Second loop: with "loop -- recur"
      (loop [i 0
             len (count pixels)]
         (if (< i len)
            (let [pix (aget pixels i)]
                (println (str i ": " pix))
                (recur (inc i)
                       len))))

Above, notice that the loop -- recur construct is essentially a let declaration, with a second call (recur) to reset the variables to something else. In this case, the next index in the array. Note how the len is simply given the same value over and over, just to avoid calling (count pixels) at each iteration.

Executing commands from the menus

Any ImageJ menu command may be run on the active image:

                                                                                                                                                                                                                 
(.doCommand ij.IJ "Add Noise")                                                                                                                                                                                        

Be aware that the above starts a new Thread and forks. For reliable control, try the run method, which will wait until the plugin finishes execution.

(.run ij.IJ "Add Noise")                                                                                                                                                                                              

Creating and using Clojure scripts as ImageJ plugins

Simply create a text file with the script inside, and place it in the plugins menu or any subfolder. Then call Plugins - Scripting - Refresh Clojure Scripts to make it appear on the menus.

If the macros/StartupMacros.txt includes a call to the Refresh Clojure Scripts inside the AutoRun macro, then all your Clojure scripts will appear automatically on startup.

To modify an script which exists already as a menu item, simply edit its file and run it by selecting it from the menus. No compiling necessary, and no need to call Refresh Clojure Scripts either (ther latter only for new scripts or at start up.)

Very important: all scripts and commands from the interpreter will run within the same thread, and within the same clojure context.

Using java beans for quick and convenient access to an object's fields

Essentially it's all about using get methods in a trivial way. For example:

(let [imp (. ij.WindowManager (getCurrentImage))
      b (bean imp)]
   (println (:title b)
            (:width b)  
            (:height b)))

Eventually Clojure may add support for set methods as well.

Appendix

  • The default output stream is at variable *out*. The Clojure Interpreter binds it to the PrintWriter that prints to the interpreter text area, by executing any typed in code within the following binding:
(binding [*out* (. getStdOut Clojure.Clojure_Interpreter)]
   ; any typed input here
   )
  • To list all existing namespaces:
>>> (all-ns)
(#<Namespace: xml> #<Namespace: zip> #<Namespace: clojure> #<Namespace: set> #<Namespace: user>)
  • To list all functions and variables of a specific namespace, first get the namespace object by name:
(ns-map (find-ns 'xml))

Note above the quoted string "xml" to avoid evaluating it to a (non-existing) value.

  • To list all functions and variables of all namespaces:
(map ns-map (all-ns))
  • To get the arguments passed to the JVM, see contents of variable *command-line-args*