In a previous post (Digital images and arrays : the same thing ?) i moaned about the lack of support of negative coordinate in image representations. In order to go beyond a simple observation, and because i need it, here is my firsts lines (of code) on this subject.
First, what are the constraints? I write my programs in Clojure. Why? Because it is a Lisp and i love Lisps. Then because it is a efficient and dynamic language, running on the jvm. This last point is important because my programs need to be portable. In ordre to be really portable, my image representation needs to be usable by a java developper. Thus, I must write a class. But as i dislike Java (the language), this class will be written in Clojure. This is a good exercise of interoperability.
Definitions
From a very generalized point of view, an image is nothing else but a bounded two-dimensional function. The imfun class will be a child of clojure.lang.IFn, the interface representing functions in Clojure. This class contains an invoke method called when an instance is used as a function. It is only needed as internal state for each instance : a definition domain and a function. Here is the beginning of Imfun:
(ns tip.Imfun
(:gen-class
:implements [clojure.lang.IFn]
:state state
:init init
:methods [[fun [] clojure.lang.IFn]
[domain [] clojure.lang.IPersistentCollection]]
:constructors {[clojure.lang.IPersistentCollection clojure.lang.IFn] []}))
Some details:
- the first line gives the used namespace:
Imfun,
:gen-class allows creation of *.class files,
- this class implements the
clojure.lang.IFn interface,
- the internal state is store in
state,
- the initialisation of an instance is realized with the
init function,
- two methods are exposed :
fun and domain,
- the constructor arguments are : a collection (for the definition domain) and a function.
The init function must returns a vector with : 1/ the arguments to be passed to the parent constructor (nothing here), and 2/ the current instance state. the init arguments must comply the ones defined with :constructor.
(defn -init [dom f]
[[] [dom f]])
The two methods
fun and
domain must returns the underlying function and the definition domain, all stored in
state.
(defn -domain [this]
(first (.state this)))
(defn -fun [this]
(second (.state this)))
The
invoke method is called when the instance is applied as a function. At this moment, the coordinates must be checked to be inside the definition domain (thanks to
indom? function). If the coordinates are outside, the value
0 is returned (this will be changed in the future).
(defn indom? [dom pt]
(let [[start end] dom
[x y] pt]
(and (>= x (first start))
(<= x (first end))
(>= y (second start))
(<= y (second end)))))
(defn -invoke
([this x y] (if (indom? (.domain this) [x y])
((.fun this) x y)
0))
([this pt] (let [[x y] pt]
(this x y))))
Files organisation and compilation
If my working directory is work, two sub-dirs must be created: src and classes. The generated *.class files will go in classes. The arborescence in src must be conforms to the namespace. Thus the previous lines of code should be put in a file work/src/tip/Imfun.clj. The compilation is then realized with:
fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e "(compile 'tip.Imfun)"
(this step can be realized in the clojure repl (read-eval-print-loop))
Use
Open a clojure repl :
fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e
Clojure 1.1.0-alpha-SNAPSHOT
user=> (import '(tip Imfun))
tip.Imfun
Let's create an image
im, being defined from
(-1, -1) to
(1, 1) and returning the sum of the coordinates values:
fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e
Clojure 1.1.0-alpha-SNAPSHOT
user=> (def f #(+ %1 %2))
'user/f
user=> (def im (Imfun. [[-1 -1] [1 1]] f))
'user/im
im is a bounded function, returning 0 outside of its definition domain.
user=> (.domain im)
[[-1 -1] [1 1]]
user=> (im -1 -1)
-2
user=> (im 0 1)
1
user=> (im 1 2)
0
It is even possible to apply
im on two coordinates
x and
y, or to a collection of two elements containing the coordinates (i.e. a point):
user=> (im 1 1)
2
user=> (im [1 1])
2
user=> (im '(1 1))
2
Here it is. Next time a second class will be written, heriting
Imfun in order to define images with values stores in a two-dimensional array. In fact, an
ImageProcessor from
ImageJ library will be used.
FMN