Plugins infrastructure

46 views
Skip to first unread message

Curtis Rueden

unread,
Dec 23, 2009, 7:24:26 PM12/23/09
to ImageJX discussion group
Hi everyone,

One of ImageJDev's goals is to enhance ImageJ's plugin model (see http://imagejdev.org/proposal, Aim IB). We have seen multiple proposals presented on this list—e.g., allowing for explicit declaration of input and output types to more easily chain plugins into a workflow. Some have even suggested eliminating the idea of "plugins" entirely (as opposed to scripts/macros), though we agree with Dimiter Prodanov that plugins are a crucial component of ImageJ's success and should be improved upon rather than removed.

Various frameworks also exist to facilitate data processing workflows—e.g., at various levels of granularity: OSGi, KNIME, and Cell Profiler. Integrating with one or more of these tools may be of benefit.

Grant Harris and I have been corresponding with the Fiji authors regarding an improved plugin interface, and Johannes Schindelin made an interesting proposal. (Albert Cardona also mentioned his "type-unsafe" TrakEM2 plugin interface: http://repo.or.cz/w/trakem2.git/blob/HEAD:/ini/trakem2/plugin/TPlugIn.java.) Since Johannes's description is a very useful addition to this discussion, I have taken the liberty of quoting it below. Johannes, does this code exist in Fiji in any concrete form yet? Or is this just something you have been thinking about?

Dimiter Prodanov also proposed a "PluginPlus" interface a while ago (see http://groups.google.com/group/imagejx/browse_thread/thread/3ac4cd10a7f1ec3c) which is simpler, and worth looking at as well.

The ImageJDev team will be playing with these ideas in more detail in coming months, but if anyone has any comments or additions in the meantime, we would be glad to discuss.

-Curtis

On Tue, Dec 15, 2009 at 5:42 PM, Johannes Schindelin <Johannes....@gmx.de> wrote:
In addition to what has been said, I'd like to add an example how a
plugin _might_ look like:

-- snip --
import org.imagej.Plugin;
import org.imagej.Image;

import org.imagej.plugin.Parameter; /* maybe it should be an inner static
       class of Plugin instead, so it does not need to be imported... */

public class GaussianBlur extends Plugin {
       @Parameter
       public double radius = 2.0;

       @Parameter(required = true)
       public Image input;

       @Parameter(output = true)
       public Image output;

       protected double kernel[];

       public void run() {
               calculateKernel();
               output = input.clone();
               blur();
       }

       /* and now the real implementation ;-) */
}
-- snap --

A possible convenience run() method in Plugin (that uses varargs,
available starting with Java5, instead of the clunky Map interface I
proposed earlier) could look like this:

-- snip --
import java.lang.reflect.Field;

public class Plugin {
       ...

       public Map<String, Object> run(Object... parameters) {
               if ((parameters.length % 2) != 0)
                       throw new IllegalArgumentException("incomplete key/value pair");
               Class clazz = getClass();
               for (int i = 0; i < parameters.length; i += 2)
                       setParameter((String)parameters[i], parameters[i + 1];
               return getOutputMap();
       }

       public void setParameter(String key, Object value) {
               try {
                       Field field = clazz.getField(key);
                       Parameter annotation = field.getAnnotation(Parameter.class);
                       if (annotation == null)
                               throw new IllegalArgumentException("field '"
                                       + name + "' is not a plugin parameter");
                       if (annotation.getOutput())
                               throw new IllegalArgumentException("field '"
                                       + name + "' is an output field");
                       field.set(this, value);
               } catch (NoSuchFieldException e) {
                       throw new IllegalArgumentException("Invalid key: " + key);
               }
       }

       public Map<String, Object> getOutputMap() {
               Map<String, Object> result = new HashMap<String, Object>();
               Class clazz = getClass();
               for (Field field : clazz) {
                       Parameter annotation = field.getAnnotation(Parameter.class);
                       if (annotation != null && annotation.getOutput())
                               result.put(field.getName(), field.get());
               return result;
       }
}
-- snap --

You would call it like that:

       display(new GaussianBlur().run("input", image, "radius", 5.0).get("output");

Of course, the plugin could also be called explicitly:

-- snip --
       ...
       GaussianBlur blur = new GaussianBlur();
       blur.input = image;
       blur.radius = 5.0;
       blur.run();
       display(blur.output);
       ...
-- snap --

The latter would be preferable for stable plugins, so that errors can be
caught at compile time, but the former would allow for rapid prototyping
(like the current plugin interface, which contributed to the popularity of
ImageJ due to being simple).

Likewise, a utility class ("InteractivePluginRunner") could construct a
dialog from the respective class fields annotated with the Parameter
class, using attributes such as "required".

The tremendous advantage would be that the plugin would not define how the
user is asked for parameters, but rather declares what parameters it takes
(and possibly some relationships, such as aspect ratio constraints for
resizing, although I did not come to a viable suggestion as to the form of
such an attribute).

For a developer, even a casual one, this should be even more appealing
than the current plugin interface, as one needs even less lines to define
the infrastructure for the algorithm.

The idea is really to entice plugin programmers to choose the new
interface because it is simpler to use, even if it is more powerful and
lends itself to reusing existing plugins by an API, and likewise by
providing easy headless operations.

[was: PluginPlus]
On Tue, Oct 6, 2009 at 5:10 PM, Dimiter <dimi...@gmail.com> wrote:
I have the following ideas about the upgrade of the current plugin
model.
The main weakness I think is that the current plugins do not define
formally inputs and outputs. In this way it is difficult to build
process chains for different analytical/processing tasks.
So here is the code, which I propose :
[see: http://groups.google.com/group/imagejx/browse_thread/thread/3ac4cd10a7f1ec3c]

[was: ImageJX, alive, dead, or just crawling for now?]
On Mon, Dec 7, 2009 at 3:51 PM, Dimiter Prodanov <dimi...@gmail.com> wrote:
Also the current plugin model is far from optimal. The main weakness
is that the plugins do not define standard inputs and outputs.

[was: Outsider's opinion]
2009/10/22 Gábor Bakos <abor...@gmail.com>
I have seen a suggestion of OSGi. I think it is tested by many people, flexible and would be a good option. About the complaint against the core plugins option: you can sign the core plugins and based on the presence or absence of that signature you can give more or less permissions (for example to access the menu/toolbox/...) for the plugins. I would definitely go on this way (I mean to have even some of the core functionalities as plugin(s)). The plus side of OSGi that you can have multiple versions of a bundle loaded to your application, so if there is an incompatibility between versions you can have more loaded.

(Dependency injection might ease the instantiation of SPIs.)

[was: Outsider's opinion]
On Fri, Oct 23, 2009 at 4:38 PM, Johan Henriksson <he.j...@gmail.com> wrote:
I have seen complaints against OSGi for being very heavy-weight
(disclaimer: I have not used it). in fact, I think the plugin hysteria
has been taken a bit too far in many cases; most of the time you just
want code to be modular and easy to add. things like loading/unloading
plugins during runtime might cost more in terms of work than it
provides. after all, this is something that has to be coded into each
plugin so it's not a pay-once cost.

given how many features you can squeeze into 1MB code (vs 10GB images),
it's hard to even motivate why you would like to download plugins
separately rather than the entire project in one go. that cuts away the
need for a plugin manager and yet another automatic updating system
(that has to be made secure).

check java 1.7 super packages if you haven't already.

[was: API vs. Scripting?]
On Wed, Dec 16, 2009 at 4:29 AM, Wilhelm Burger <wil...@ieee.org> wrote:
* In the context of refurbishing the plugin model I was thinking about
the role of plugins in general. Initially (I believe), IJ had no
scripting functionality and writing plugins was the only way to extend
IJ's builtin capabilities. At the same time, plugins have been used as
a substitute for enhancing the IJ API, with the disadvantage of
loosing compile-time safety features. I would argue that this should
be better done by adding the corresponding functionality to the API
itself.

* But given that scripting is available (and preferred by many users)
to implement specific functionalities, is there still a role for
plugins? If IJ had a comprehensive and clean API + scripting (as was
said in another post), are plugins in their current form needed at
all? My opinion is no, scripting should be sufficient, preferably
using a well-documented, "real" standard language with a broad
knowledge base and transparent access to Java components.

[was: API vs. Scripting?]
On Wed, Dec 16, 2009 at 4:46 AM, Dimiter Prodanov <dimi...@gmail.com> wrote:
I totally disagree with the idea of abandoning plugins.
In fact the easy extensibility with plugins according to me is one of
the historical advantages of ImageJ. Almost the entire IJ is plugins.
If you abandon the plugins many people will switch to versions/distros
of IJ which support them

Gábor Bakos

unread,
Dec 24, 2009, 8:16:17 AM12/24/09
to ima...@googlegroups.com
Hi Curtis,

2009/12/24 Curtis Rueden <ctrued...@gmail.com>

Hi everyone,

One of ImageJDev's goals is to enhance ImageJ's plugin model (see http://imagejdev.org/proposal, Aim IB). We have seen multiple proposals presented on this list—e.g., allowing for explicit declaration of input and output types to more easily chain plugins into a workflow. Some have even suggested eliminating the idea of "plugins" entirely (as opposed to scripts/macros), though we agree with Dimiter Prodanov that plugins are a crucial component of ImageJ's success and should be improved upon rather than removed.
I agree with this.


Various frameworks also exist to facilitate data processing workflows—e.g., at various levels of granularity: OSGi, KNIME, and Cell Profiler. Integrating with one or more of these tools may be of benefit.
I might also add Pipeline Pilot, Inforsense and MyGrid. Using OSGi for workflows, might be too low level, but it is ok for plugins.
Bests, --g
--
Of all the things I've lost, I miss my mind the most. ~~~ Mark Twain

Wilhelm Burger

unread,
Dec 24, 2009, 12:03:36 PM12/24/09
to ImageJX, ctrued...@gmail.com, wil...@ieee.org
Hello Curtis & friends,

On Dec 24, 1:24 am, Curtis Rueden <ctrueden.w...@gmail.com> wrote:
> Hi everyone,
>

> One of ImageJDev's goals is to enhance ImageJ's plugin model (seehttp://imagejdev.org/proposal, Aim IB). We have seen multiple proposals


> presented on this list—e.g., allowing for explicit declaration of input and
> output types to more easily chain plugins into a workflow. Some have even
> suggested eliminating the idea of "plugins" entirely (as opposed to
> scripts/macros), though we agree with Dimiter Prodanov that plugins are a
> crucial component of ImageJ's success and should be improved upon rather
> than removed.

Actually I did not want "suggest to eliminate plugins" but to discuss
the general role of plugins in IJ and, in particular, how this role is
different to what is being done (can be done) with scripting.
Personally I prefer plugins too but I would still love to hear
concrete arguments rather than just accepting that this is not an
option.

Nevertheless, back in March 2009 I sent Grant Harris a sketch of a
demo implementation for an overhauled plugin mechanism for IJ. Since
this proposal never made it into the public I would like to throw it
into the discussion now. I cleaned it a bit today (had not looked at
it since) and you can find the thing here:

http://groups.google.com/group/imagejx/web/Wilburs-IJ-Plugins-Proposal.zip

This is a *very* rudimental implementation of a simplified yet
hopefully more secure
plugin mechanism for ImageJ. It is largely uncommented and only
intended as a toy so
far.

Usage: Expand the ZIP file inside IJ's plugins folder. The top-level
folder contains two files:
- "CompileAndRun_Wij.java": this is a regular IJ plugin that compiles
and loads another
plugin written in my new "Wij" (Wilbur's IJ) format. This class
contains a modified copy
of IJ's "Compiler" class but uses the new standard JDK compiler (not
the tools-compiler).
- "DemoInverter_Wij.java": this is a plugin in the new style. It
inverts

My main focus was on the basic image data structures (no more
"processor") and the
plugin mechanism. The image data structures are very simple and share
the 1D pixel array
with the existing ImageProcessor classes. To avoid type casting, the
getPixel() and
putPixel() methods exist only in the real subclasses with different
signatures -
they cannot be applied to a generic "WijImage" object (which I think
would be a flaw)!
Displaying etc. is handled by "old" IJ.

Wrt. plugins, I wanted that plugins are never called with arguments
they cannot handle.
Also I wanted to get rid of the bit-flags returned by the setup()
method to indicate
the plugin's capabilities. Instead, each plugin implements a separate
interface for every
type of argument it is supposed to handle and provides a separate run
() method for each type.
For example, the "Demo_Inverter_Wij" class has 2 run methods:
run(Wij_ImageByte im)
run(Wij_ImageFloat im)
Thus the run() method does not need to check which type of object it
is passed to, instead,
the dispatching is done by method overloading.

Currently the run() methods returns nothing (void) but it would be
simple to change this
to return some instance of a class implementing a "WijPluginResult"
interface (say) and
return null if nothing is returned.

Also note that the new duplicate() method does not require it result
to be typecast (see
the example in "Demo_Inverter_Wij.java".


- Hope you find your way through. Please note that it is only a
scribble, far from being complete or clean.
Have a look and let me know what you think.

--Wilhelm

Dimiter Prodanov

unread,
Dec 24, 2009, 3:13:05 PM12/24/09
to ima...@googlegroups.com, ctrued...@gmail.com, wil...@ieee.org
Hello,

and Merry Christmas!

One of the things we have to keep in mind is the proper image
calibration. I run into this over and over when I try to process
intensity calibrated images from MRI or CT.
The pixel values themselves are not so useful but the calibrated values are.
So I would suggest to go revolutionary and adopt max. three underlying
image formats and transform everything back and forth through
performance-optimized code by means of intensity calibration.
Such natural candidates are 8-bit int (a lot of legacy code), 32-bit
float (can handle float, 16 bit signed and unsigned) and may be 64bit
float or int arrays. In such way an algorithm should be implemented
max 3 times if generics would not become possible.


best regards,

Dimiter

> --
>
> You received this message because you are subscribed to the Google Groups "ImageJX" group.
> To post to this group, send email to ima...@googlegroups.com.
> To unsubscribe from this group, send email to imagejx+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/imagejx?hl=en.
>
>
>

Johan Henriksson

unread,
Dec 24, 2009, 6:38:51 PM12/24/09
to ima...@googlegroups.com
On Thu, Dec 24, 2009 at 9:13 PM, Dimiter Prodanov <dimi...@gmail.com> wrote:
Hello,

and Merry Christmas!

One of the things we have to keep in mind is the proper image
calibration. I run into this over and over when I try to process
intensity calibrated images from MRI or CT.
The pixel values themselves are not so useful but the calibrated values are.
So I would suggest to go revolutionary and adopt max. three underlying
image formats and transform everything back and forth through
performance-optimized code by means of intensity calibration.

can you elaborate on what you mean by intensity calibration? is this deciding how to map values to screen intensity? check below if my reasoning makes sense.

been giving this quite some thought and found that values should only be understood as photon counts, and as such has no maximum. yes, the sensor can be saturated but you should never let it go that far. and the integer limit has nothing to do with physics and certainly not the screen 0-255-range (less obvious if you come from the computer graphics paradigm).

the implications are important; it means e.g. that a conversion from int32 to int16 is just an =, no rescaling (since the maximum can't change, there is no physical maximum), and if it overflows then it's a user error. if a user has a reason to put data in a smaller data type then it is his/her responsibility to ensure that it works out fine e.g. by doing a division if needed.

cost: it affects the image viewer as data has to be scaled on-the-fly to be visible to the user. but then this is essentially always needed anyway to explore the data (endrov always shows contrast/brightness sliders, probably to be replaced by some histogram mapper. micromanager is similar)

gain: fast and simple conversion. no loss of data by default if the data fits in the new type. makes more sense for floating point data since it has no "real" max/min-limits (but 0.0-1.0 is sort of intuitive). makes a lot more sense for negative intensities (common byproduct from image operators). generally easier to keep track of ranges (or the lack of them) when mixing types in one operation.

/Johan

--
-----------------------------------------------------------
Johan Henriksson
PhD student, Karolinska Institutet
http://mahogny.areta.org  http://www.endrov.net

Johannes Schindelin

unread,
Dec 25, 2009, 6:32:09 PM12/25/09
to Curtis Rueden, ImageJX discussion group
Hi,

On Wed, 23 Dec 2009, Curtis Rueden wrote:

> Grant Harris and I have been corresponding with the Fiji authors
> regarding an improved plugin interface, and Johannes Schindelin made an
> interesting proposal. (Albert Cardona also mentioned his "type-unsafe"
> TrakEM2 plugin interface:
> http://repo.or.cz/w/trakem2.git/blob/HEAD:/ini/trakem2/plugin/TPlugIn.java.)
> Since Johannes's description is a very useful addition to this
> discussion, I have taken the liberty of quoting it below. Johannes, does
> this code exist in Fiji in any concrete form yet? Or is this just
> something you have been thinking about?

I have been thinking about an annotation-based approach for some time now,
but due to other obligations, did not have time to implement anything
concretely yet. Hopefully that will change in a few weeks (when I can get
sidetracked from working on proper documentation on the image library by
the plugins interface :-)

Ciao,
Johannes

Johannes Schindelin

unread,
Dec 25, 2009, 6:41:06 PM12/25/09
to Wilhelm Burger, ImageJX, ctrued...@gmail.com
Hi,

On Thu, 24 Dec 2009, Wilhelm Burger wrote:

> My main focus was on the basic image data structures (no more
> "processor") and the plugin mechanism.

I think that it would be good to keep the architecture of the plugins
completely separate from the architecture of the data handling.

Ciao,
Johannes

Johannes Schindelin

unread,
Jan 12, 2010, 11:29:58 AM1/12/10
to Curtis Rueden, ImageJX discussion group, Jean-Yves Tinevez, Fiji-devel

[merged two threads from two mailing lists...]

On Sat, 26 Dec 2009, Johannes Schindelin wrote:

> I have been thinking about an annotation-based approach for some time
> now, but due to other obligations, did not have time to implement
> anything concretely yet. Hopefully that will change in a few weeks
> (when I can get sidetracked from working on proper documentation on the
> image library by the plugins interface :-)

Okay, so here it goes...

I described the plugin architecture in a rather hand-waving manner before
Christmas, so I thought I should put my money where my mouth is and
demonstrate that it actually works.

This patch series was developed inside fiji.git, as this framework is what
I am most familiar with. You can see it in gitweb here (the latest 3
patches):
http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=shortlog;h=refs/heads/abstract-plugin

The first commit lays the ground-work, the second adds two convenience classes,
and the third just adds the example (so you might want to look at that first).

As suggested by Albert and Curtis, there is an abstract base class using
static methods from a helper class, but if you need to extend another
class for your plugin, all you have to do is to implement the
java.lang.Runnable interface (basically, a public void run() method) and
use the RunnableAdapter to make a proper ImageJ plugin.

Maybe PlugInException should extend RuntimeException rather than Exception
so that the run() method can throw it, too (without changing the interface
of Runnable, or adding another interface).

I look forward to your comments,
Johannes

Johannes Schindelin (3):
A versatile abstract plugin class
Add convenience classes to wrap Runnable instances into PlugIns
Add an example for a new plugin using the fiji.plugin.Parameter
method

Fakefile | 2 +-
src-plugins/Fiji_Plugins/test/Example_PlugIn.java | 15 ++
.../fiji-lib/fiji/plugin/AbstractPlugIn.java | 27 +++
src-plugins/fiji-lib/fiji/plugin/Parameter.java | 13 ++
.../fiji-lib/fiji/plugin/PlugInException.java | 11 +
.../fiji-lib/fiji/plugin/PlugInFunctions.java | 208 ++++++++++++++++++++
.../fiji-lib/fiji/plugin/PlugInWrapper.java | 23 +++
.../fiji-lib/fiji/plugin/RunnableAdapter.java | 35 ++++
staged-plugins/Fiji_Plugins.config | 2 +
9 files changed, 335 insertions(+), 1 deletions(-)
create mode 100644 src-plugins/Fiji_Plugins/test/Example_PlugIn.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/AbstractPlugIn.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/Parameter.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInException.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInFunctions.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInWrapper.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/RunnableAdapter.java

Johannes Schindelin

unread,
Jan 12, 2010, 11:30:33 AM1/12/10
to Curtis Rueden, ImageJX discussion group, Jean-Yves Tinevez, Fiji-devel

As discussed on the ImageJX list. The idea is to store input/output
parameters as fields of the plugin class. They must be annotated
(with fiji.plugin.Parameter) so that a dialog can be constructed at
runtime.

Annotated in such a way, the parameters can also be set from a
map containing key/value pairs; this is only recommended if you need
the flexibility, or if you need to test quickly, as it moves the
compile time validation to a runtime validation.

Using this to make a plugin is easy: just implement the run()
method of a Runnable, and extend AbstractPlugIn:

import fiji.plugin.AbstractPlugIn;

public class MyPlugIn extends AbstractPlugIn {
public void run() {
IJ.log("Hello, World");
}
}

To use a parameter, just add a field:

import fiji.plugin.Parameter;
...

@Parameter public String Name;

This will make a proper ImageJ plugin which shows a dialog constructed from
the annotated input parameters at runtime before it runs the run() method.

As this is only a proof of concept at this point, the only supported
input parameter type is a String, and the only supported output
parameter is an ImagePlus.

Signed-off-by: Johannes Schindelin <johannes....@gmx.de>
---


.../fiji-lib/fiji/plugin/AbstractPlugIn.java | 27 +++
src-plugins/fiji-lib/fiji/plugin/Parameter.java | 13 ++
.../fiji-lib/fiji/plugin/PlugInException.java | 11 +
.../fiji-lib/fiji/plugin/PlugInFunctions.java | 208 ++++++++++++++++++++

4 files changed, 259 insertions(+), 0 deletions(-)


create mode 100644 src-plugins/fiji-lib/fiji/plugin/AbstractPlugIn.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/Parameter.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInException.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInFunctions.java

diff --git a/src-plugins/fiji-lib/fiji/plugin/AbstractPlugIn.java b/src-plugins/fiji-lib/fiji/plugin/AbstractPlugIn.java
new file mode 100644
index 0000000..2d015d0
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/AbstractPlugIn.java
@@ -0,0 +1,27 @@
+package fiji.plugin;
+
+import ij.plugin.PlugIn;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractPlugIn implements PlugIn, Runnable {
+ public void run(String arg) {
+ PlugInFunctions.runInteractively(this);
+ }
+
+ public abstract void run();
+
+ public Map<String, Object> run(Object... parameters)
+ throws PlugInException {
+ return PlugInFunctions.run(this, parameters);
+ }
+
+ public void setParameter(String key, Object value) {
+ PlugInFunctions.setParameter(this, key, value);
+ }
+
+ public Map<String, Object> getOutputMap() {
+ return PlugInFunctions.getOutputMap(this);
+ }
+}
diff --git a/src-plugins/fiji-lib/fiji/plugin/Parameter.java b/src-plugins/fiji-lib/fiji/plugin/Parameter.java
new file mode 100644
index 0000000..116e7bd
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/Parameter.java
@@ -0,0 +1,13 @@
+package fiji.plugin;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Parameter {
+ String label() default "";
+ boolean isOutput() default false;
+}
diff --git a/src-plugins/fiji-lib/fiji/plugin/PlugInException.java b/src-plugins/fiji-lib/fiji/plugin/PlugInException.java
new file mode 100644
index 0000000..d80360b
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/PlugInException.java
@@ -0,0 +1,11 @@
+package fiji.plugin;
+
+public class PlugInException extends Exception {
+ public PlugInException() {
+ super();
+ }
+
+ public PlugInException(String reason) {
+ super(reason);
+ }
+}
diff --git a/src-plugins/fiji-lib/fiji/plugin/PlugInFunctions.java b/src-plugins/fiji-lib/fiji/plugin/PlugInFunctions.java
new file mode 100644
index 0000000..05f04ab
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/PlugInFunctions.java
@@ -0,0 +1,208 @@
+package fiji.plugin;
+
+import ij.ImagePlus;
+
+import ij.gui.GenericDialog;
+
+import java.lang.reflect.Field;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class PlugInFunctions {
+ public static Map<String, Object> run(Runnable plugin,
+ Object... parameters) throws PlugInException {
+ if ((parameters.length % 2) != 0)
+ throw new IllegalArgumentException("incomplete key/value pair");
+ Class clazz = plugin.getClass();
+ for (int i = 0; i < parameters.length; i += 2)
+ setParameter(plugin,
+ (String)parameters[i], parameters[i + 1]);
+ plugin.run();
+ return getOutputMap(plugin);
+ }
+
+ public static void setParameter(Runnable plugin,
+ String key, Object value) {
+ try {
+ Class clazz = plugin.getClass();
+ Field field = clazz.getField(key);
+ Parameter annotation =
+ field.getAnnotation(Parameter.class);
+ if (annotation == null)
+ throw new IllegalArgumentException("field '"
+ + key + "' is not a plugin parameter");
+ if (annotation.isOutput())
+ throw new IllegalArgumentException("field '"
+ + key + "' is an output field");
+ field.set(plugin, value);
+ } catch (NoSuchFieldException e) {
+ throw new IllegalArgumentException("Invalid key: " + key);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Field is not public: " + key);
+ }
+ }
+
+ public static Map<String, Object> getOutputMap(Runnable plugin) {
+ Map<String, Object> result = new HashMap<String, Object>();
+ for (Field field : getOutputParameters(plugin)) try {
+ result.put(field.getName(), field.get(plugin));
+ } catch (Exception e) { e.printStackTrace(); }
+ return result;
+ }
+
+ public static void runInteractively(Runnable plugin) {
+ if (!showDialog(plugin))
+ return;
+ plugin.run();
+ for (ImagePlus image : PlugInFunctions.getOutputImages(plugin))
+ image.show();
+ }
+
+ public static String getLabel(Field field) {
+ Parameter parameter = field.getAnnotation(Parameter.class);
+ if (parameter != null) {
+ String label = parameter.label();
+ if (label != null && !label.equals(""))
+ return label;
+ }
+ return field.getName();
+ }
+
+ public static Object getDefault(Field field) {
+ // TODO
+ return "";
+ }
+
+ public static boolean showDialog(Runnable plugin) {
+ // TODO: Should plugin have a getName() method, defaulting
+ // to the class name?
+ GenericDialog dialog = new GenericDialog("Parameters");
+ for (Field field : getInputParameters(plugin)) {
+ if (field.getType() == String.class)
+ dialog.addStringField(getLabel(field),
+ (String)getDefault(field));
+ else
+ throw new RuntimeException("TODO!");
+ }
+ dialog.showDialog();
+ if (dialog.wasCanceled())
+ return false;
+ for (Field field : getInputParameters(plugin)) try {
+ if (field.getType() == String.class)
+ field.set(plugin, dialog.getNextString());
+ else
+ throw new RuntimeException("TODO!");
+ } catch (Exception e) { e.printStackTrace(); }
+ return true;
+ }
+
+ public interface ParameterFilter {
+ public boolean matches(Parameter parameter);
+ }
+
+ protected final static ParameterFilter all = new ParameterFilter() {
+ public boolean matches(Parameter parameter) {
+ return true;
+ }
+ };
+
+ protected final static ParameterFilter inputs = new ParameterFilter() {
+ public boolean matches(Parameter parameter) {
+ return !parameter.isOutput();
+ }
+ };
+
+ protected final static ParameterFilter outputs = new ParameterFilter() {
+ public boolean matches(Parameter parameter) {
+ return parameter.isOutput();
+ }
+ };
+
+ protected static class ParameterIterable implements Iterable<Field> {
+ Field[] fields;
+ ParameterFilter filter;
+
+ ParameterIterable(Field[] fields, ParameterFilter filter) {
+ this.fields = fields;
+ this.filter = filter;
+ }
+
+ ParameterIterable(Runnable plugin,
+ ParameterFilter filter) {
+ this(plugin.getClass().getFields(), filter);
+ }
+
+ public Iterator<Field> iterator() {
+ return new ParameterIterator(fields, filter);
+ }
+ }
+
+ protected static class ParameterIterator implements Iterator<Field> {
+ int counter;
+ Field[] fields;
+ ParameterFilter filter;
+
+ ParameterIterator(Field[] fields, ParameterFilter filter) {
+ this.fields = fields;
+ this.filter = filter;
+ counter = -1;
+ findNext();
+ }
+
+ void findNext() {
+ while (++counter < fields.length) {
+ Parameter parameter = fields[counter]
+ .getAnnotation(Parameter.class);
+ if (parameter == null)
+ continue;
+ if (filter.matches(parameter))
+ return;
+ }
+ }
+
+ public boolean hasNext() {
+ return counter < fields.length;
+ }
+
+ public Field next() {
+ Field result = fields[counter];
+ findNext();
+ return result;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public static Iterable<Field> getParameters(Runnable plugin,
+ ParameterFilter filter) {
+ return new ParameterIterable(plugin, filter);
+ }
+
+ public static Iterable<Field> getParameters(Runnable plugin) {
+ return getParameters(plugin, all);
+ }
+
+ public static Iterable<Field> getInputParameters(Runnable plugin) {
+ return getParameters(plugin, inputs);
+ }
+
+ public static Iterable<Field> getOutputParameters(Runnable plugin) {
+ return getParameters(plugin, outputs);
+ }
+
+ public static Iterable<ImagePlus> getOutputImages(Runnable plugin) {
+ List<ImagePlus> result = new ArrayList<ImagePlus>();
+ for (Field field : getOutputParameters(plugin)) {
+ if (field.getType() == ImagePlus.class) try {
+ result.add((ImagePlus)field.get(plugin));
+ } catch (Exception e) { e.printStackTrace(); }
+ }
+ return result;
+ }
+}
--
1.6.4.297.gcb4cc


Johannes Schindelin

unread,
Jan 12, 2010, 11:30:51 AM1/12/10
to Curtis Rueden, ImageJX discussion group, Jean-Yves Tinevez, Fiji-devel

With the new concept of input/output parameters being encoded as annotated
fields, all that is left of the generic plugin interface is a public run()
method, i.e. the Runnable interface.

Provide ways (time will tell which ones are the most useful) to wrap
Runnables as plugins:

PlugIn plugin = new RunnableAdapter(new MyPlugIn());

or

public class Wrapped_PlugIn extends RunnableAdapter {
public Wrapped_PlugIn() {
super(new MyPlugIn());
}
}

or

public class Wrapped_PlugIn extends PlugInWrapper {
public Wrapped_PlugIn() {
super("MyPlugIn");
}
}

or in a plugins.config:

Bla>Blub, "Blizz", fiji.plugin.PlugInWrapper("MyPlugIn")

Unfortunately, it is not possible to do this with generics
(Bla>Blub, "Blizz", fiji.plugin.PlugInWrapper<MyPlugIn>), because
Java generics work by erasure. Therefore, a PlugInWrapper<T>
cannot instantiate T.

But a possible option is to teach ImageJA to fall back to using the
PlugInWrapper if it finds that class and the plugin is an instance
of a Runnable.

Signed-off-by: Johannes Schindelin <johannes....@gmx.de>
---

.../fiji-lib/fiji/plugin/PlugInWrapper.java | 23 +++++++++++++
.../fiji-lib/fiji/plugin/RunnableAdapter.java | 35 ++++++++++++++++++++
2 files changed, 58 insertions(+), 0 deletions(-)


create mode 100644 src-plugins/fiji-lib/fiji/plugin/PlugInWrapper.java
create mode 100644 src-plugins/fiji-lib/fiji/plugin/RunnableAdapter.java

diff --git a/src-plugins/fiji-lib/fiji/plugin/PlugInWrapper.java b/src-plugins/fiji-lib/fiji/plugin/PlugInWrapper.java
new file mode 100644
index 0000000..2b0e3d2
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/PlugInWrapper.java
@@ -0,0 +1,23 @@
+package fiji.plugin;
+
+import ij.IJ;
+
+public class PlugInWrapper extends RunnableAdapter {
+ public PlugInWrapper() {
+ super(null);
+ }
+
+ public void run(String arg) {
+ try {
+ Class clazz = IJ.getClassLoader().loadClass(arg);
+ plugin = (Runnable)clazz.newInstance();
+ super.run(arg);
+ } catch (ClassNotFoundException e) {
+ IJ.error("Could not find class '" + arg + "'");
+ } catch (InstantiationException e) {
+ IJ.error("Could not instantiate class '" + arg + "'");
+ } catch (IllegalAccessException e) {
+ IJ.error("Could not access constructor of '" + arg + "'");
+ }
+ }
+}
diff --git a/src-plugins/fiji-lib/fiji/plugin/RunnableAdapter.java b/src-plugins/fiji-lib/fiji/plugin/RunnableAdapter.java
new file mode 100644
index 0000000..394213c
--- /dev/null
+++ b/src-plugins/fiji-lib/fiji/plugin/RunnableAdapter.java
@@ -0,0 +1,35 @@


+package fiji.plugin;
+
+import ij.plugin.PlugIn;
+

+import java.util.Map;
+
+public class RunnableAdapter extends AbstractPlugIn {
+ Runnable plugin;
+
+ public RunnableAdapter(Runnable plugin) {
+ this.plugin = plugin;
+ }
+
+ public void run() {
+ plugin.run();
+ }
+
+ public void runInteractively() {
+ PlugInFunctions.runInteractively(plugin);
+ }


+
+ public Map<String, Object> run(Object... parameters)
+ throws PlugInException {

+ return PlugInFunctions.run(plugin, parameters);
+ }
+
+ public void setParameter(String key, Object value) {
+ PlugInFunctions.setParameter(plugin, key, value);
+ }
+
+ public Map<String, Object> getOutputMap() {
+ return PlugInFunctions.getOutputMap(plugin);
+ }
+}
+
--
1.6.4.297.gcb4cc


Johannes Schindelin

unread,
Jan 12, 2010, 11:31:19 AM1/12/10
to Curtis Rueden, ImageJX discussion group, Jean-Yves Tinevez, Fiji-devel

The name of the field is used for the implicitely-defined dialog, so
it must be written using the correct case; underscores will be
transformed into spaces by ImageJ.

Signed-off-by: Johannes Schindelin <johannes....@gmx.de>
---

Fakefile | 2 +-
src-plugins/Fiji_Plugins/test/Example_PlugIn.java | 15 +++++++++++++++
staged-plugins/Fiji_Plugins.config | 2 ++
3 files changed, 18 insertions(+), 1 deletions(-)
create mode 100644 src-plugins/Fiji_Plugins/test/Example_PlugIn.java

diff --git a/Fakefile b/Fakefile
index 18cedab..b9972f7 100644
--- a/Fakefile
+++ b/Fakefile
@@ -239,7 +239,7 @@ plugins/LSM_Toolbox.jar <- \
src-plugins/LSM_Toolbox/**/*.txt
MAINCLASS(plugins/Interactive_3D_Surface_Plot.jar)=Interactive_3D_Surface_Plot
CLASSPATH(plugins/Stitching_.jar)=plugins/loci_tools.jar:plugins/Fiji_Plugins.jar
-CLASSPATH(plugins/Fiji_Plugins.jar)=jars/jsch-0.1.37.jar
+CLASSPATH(plugins/Fiji_Plugins.jar)=jars/jsch-0.1.37.jar:jars/fiji-lib.jar
CLASSPATH(plugins/Fiji_Updater.jar)=jars/jsch-0.1.37.jar:misc/Fiji.jar

plugins/Record_Screen.jar <- src-plugins/Record_Screen/ src-plugins/Record_Screen/**/*
diff --git a/src-plugins/Fiji_Plugins/test/Example_PlugIn.java b/src-plugins/Fiji_Plugins/test/Example_PlugIn.java
new file mode 100644
index 0000000..1026b26
--- /dev/null
+++ b/src-plugins/Fiji_Plugins/test/Example_PlugIn.java
@@ -0,0 +1,15 @@
+package test;
+
+import fiji.plugin.AbstractPlugIn;
+import fiji.plugin.Parameter;
+
+import ij.IJ;
+
+public class Example_PlugIn extends AbstractPlugIn {
+ /* the name will be used for the dialog, so it starts upcased. */
+ @Parameter public String First_name;


+
+ public void run() {

+ IJ.showMessage("Good morning, " + First_name + "!");
+ }
+}
diff --git a/staged-plugins/Fiji_Plugins.config b/staged-plugins/Fiji_Plugins.config
index 159c6f4..5698dc8 100644
--- a/staged-plugins/Fiji_Plugins.config
+++ b/staged-plugins/Fiji_Plugins.config
@@ -12,3 +12,5 @@ Image>Stacks, "Dynamic Reslice", fiji.stacks.Dynamic_Reslice
Edit>Selection, "Fit Circle to Image", fiji.util.Circle_Fitter

Help, "Upload Sample Image", fiji.util.Fiji_Uploader
+
+Test, "Abstract PlugIn Test", test.Example_PlugIn
--
1.6.4.297.gcb4cc


Dimiter Prodanov

unread,
Jan 12, 2010, 11:48:56 AM1/12/10
to ima...@googlegroups.com
Hi Johannes,

Could you post a jar or something?
I looked at your mails but I am not so good at reading diff

-- why do you declare static run()?
-- why do you return the Map from the run ( public Map<String, Object>
run(Object... parameters) )

For me simpler approach will be to pass an input Map in the run().
I don't have much experience with Annotations so I don't see their utility here.

best regards,

Dimiter

Johannes Schindelin

unread,
Jan 12, 2010, 12:36:32 PM1/12/10
to Dimiter Prodanov, ima...@googlegroups.com, fiji-...@googlegroups.com
Hi,


was it intentional that you removed e.g. the fiji-devel list from the Cc:?
I re-added it assuming that this was unintentional.

However, I did not re-add those from the Cc: list that you stripped out,
as I really do not have the time to undo your stripping (actually not even
to write this mail, but what the heck).


On Tue, 12 Jan 2010, Dimiter Prodanov wrote:

> Could you post a jar or something?

Later, I have to run after writing this mail.

> I looked at your mails but I am not so good at reading diff

That is why I also provided a link to the gitweb, where you can choose to
look at the files themselves. Direct links to the files for your
convenience:

http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=tree;f=src-plugins/fiji-lib/fiji/plugin;h=e27791e4bca82b8674e341060db71d6179439cdd;hb=abstract-plugin

and the example plugin:

http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=blob;f=src-plugins/Fiji_Plugins/test/Example_PlugIn.java;h=1026b262dd3e851bb176b8e2b4b880cbadfc01f7;hb=abstract-plugin

> -- why do you declare static run()?

You mean abstract? Because it needs to be implemented for each plugin,
and it is obviously different for each one plugin, too.

I do not find any other declaration of a run method that does not take
arguments. Maybe I misunderstand?

> -- why do you return the Map from the run ( public Map<String, Object>
> run(Object... parameters) )

This is just the convenience method wished for by Albert. In general, I
try to restrict users in the least possible way (I heard that this is also
implied by something called Postel's Law).

The map is required because you can have more than one output. Think, for
example, split channels.

In general, the run() method is void, as the output parameters are meant
to be fields of the plugin class.

> For me simpler approach will be to pass an input Map in the run().

We can add such a method, but it is often easier to read things like this:

ImagePlus image = (ImagePlus)
new GaussianBlur().run("image", image, "radius", 2.0)
.get("output");

than

Map<String, Object> map = new HashMap<String, Object>();
map.put("image", image);
map.put("radius", 2.0);
ImagePlus image = (ImagePlus)plugin.run(map).get("output");

I sincerely hope that you agree.

> I don't have much experience with Annotations so I don't see their
> utility here.

The utility is that you no longer have to construct a generic dialog
yourself. The utility is that you can specify with the declaration of the
field what its properties are. The utility is that not only ImageJ, but
also your Java code can discover what parameters are available, without
having to adjust code, let alone having to recompile it, when the plugin
changes. There are other benefits, too. Just think about it for a while.

See?
Johannes

Dimiter Prodanov

unread,
Jan 13, 2010, 5:12:58 AM1/13/10
to Johannes Schindelin, ima...@googlegroups.com, fiji-...@googlegroups.com
On Tue, Jan 12, 2010 at 6:36 PM, Johannes Schindelin
<Johannes....@gmx.de> wrote:
> Hi,
>
>
> was it intentional that you removed e.g. the fiji-devel list from the Cc:?
> I re-added it assuming that this was unintentional.
>
> However, I did not re-add those from the Cc: list that you stripped out,
> as I really do not have the time to undo your stripping (actually not even
> to write this mail, but what the heck).

Nope, I hit the reply to button and it posted only to the imagejx google-group.
Thanks for the links I will try them.


> On Tue, 12 Jan 2010, Dimiter Prodanov wrote:
>
>> Could you post a jar or something?
>
> Later, I have to run after writing this mail.
>
>> I looked at your mails but I am not so good at reading diff
>
> That is why I also provided a link to the gitweb, where you can choose to
> look at the files themselves.  Direct links to the files for your
> convenience:
>
> http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=tree;f=src-plugins/fiji-lib/fiji/plugin;h=e27791e4bca82b8674e341060db71d6179439cdd;hb=abstract-plugin
>
> and the example plugin:
>
> http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=blob;f=src-plugins/Fiji_Plugins/test/Example_PlugIn.java;h=1026b262dd3e851bb176b8e2b4b880cbadfc01f7;hb=abstract-plugin
>
>> -- why do you declare static run()?
>
> You mean abstract?  Because it needs to be implemented for each plugin,
> and it is obviously different for each one plugin, too.

May be I read wrongly the source.

>
> I do not find any other declaration of a run method that does not take
> arguments.  Maybe I misunderstand?
>
>> -- why do you return the Map from the run ( public Map<String, Object>
>> run(Object... parameters) )
>
> This is just the convenience method wished for by Albert.  In general, I
> try to restrict users in the least possible way (I heard that this is also
> implied by something called Postel's Law).

OK, if they want it badly ;)

>
> The map is required because you can have more than one output.  Think, for
> example, split channels.

This I understand very well.


>
> In general, the run() method is void, as the output parameters are meant
> to be fields of the plugin class.
>
>> For me simpler approach will be to pass an input Map in the run().
>
> We can add such a method, but it is often easier to read things like this:
>
>        ImagePlus image = (ImagePlus)
>                new GaussianBlur().run("image", image, "radius", 2.0)
>                        .get("output");

Now, imaging the same but with 20 parameters. I don't think it will
give much convenience.

>
> than
>
>        Map<String, Object> map = new HashMap<String, Object>();
>        map.put("image", image);
>        map.put("radius", 2.0);
>        ImagePlus image = (ImagePlus)plugin.run(map).get("output");
>
> I sincerely hope that you agree.

This is the reason, I suggested registerInputs(whatever here) method.


>
>> I don't have much experience with Annotations so I don't see their
>> utility here.
>
> The utility is that you no longer have to construct a generic dialog
> yourself.  The utility is that you can specify with the declaration of the
> field what its properties are.  The utility is that not only ImageJ, but
> also your Java code can discover what parameters are available, without
> having to adjust code, let alone having to recompile it, when the plugin
> changes.  There are other benefits, too.  Just think about it for a while.
>
> See?

I will look into those new features. Thanks.

> Johannes
>
>

best regards,

Dimiter

Johannes Schindelin

unread,
Jan 13, 2010, 5:57:19 AM1/13/10
to Dimiter Prodanov, ima...@googlegroups.com, fiji-...@googlegroups.com
Hi Dimiter,

On Wed, 13 Jan 2010, Dimiter Prodanov wrote:

> On Tue, Jan 12, 2010 at 6:36 PM, Johannes Schindelin
> <Johannes....@gmx.de> wrote:
>
> > On Tue, 12 Jan 2010, Dimiter Prodanov wrote:
> >
> >> Could you post a jar or something?
> >
> > Later, I have to run after writing this mail.

Attached.

It is in the form of Fiji_Plugins.jar (containing the example plugin,
being installed into the new Test menu) and fiji-lib.jar, which contains
the actual implementation. For obvious reasons, I only tested in Fiji,
but it should work with ImageJ all the same.

> > In general, the run() method is void, as the output parameters are
> > meant to be fields of the plugin class.
> >
> >> For me simpler approach will be to pass an input Map in the run().
> >
> > We can add such a method, but it is often easier to read things like this:
> >
> > � � � �ImagePlus image = (ImagePlus)
> > � � � � � � � �new GaussianBlur().run("image", image, "radius", 2.0)
> > � � � � � � � � � � � �.get("output");
>
> Now, imaging the same but with 20 parameters. I don't think it will
> give much convenience.

Right. But the architecture is not about forcing a single way to specify
the parameters. It is about providing convenient methods to run the
plugins. Some plugins need few parameters, for which the illustrated
method is the quickest way. Some plugins need lots of parameters, in
which case a Map, or setting the fields of the plugin class directly, is
the best way.

> > than
> >
> > � � � �Map<String, Object> map = new HashMap<String, Object>();
> > � � � �map.put("image", image);
> > � � � �map.put("radius", 2.0);
> > � � � �ImagePlus image = (ImagePlus)plugin.run(map).get("output");
> >
> > I sincerely hope that you agree.
>
> This is the reason, I suggested registerInputs(whatever here) method.

The problem with the registerInputs() method is that you have to call it
at runtime (annotations are put in at compile time, and accessible after
loading the class), and that you have to specify an attribute of the class
member somewhere else than where you declare it. That is inconvenient,
and does not help readers to understand the code.

Ciao,
Johannes

Fiji_Plugins.jar
fiji-lib.jar

Wilhelm Burger

unread,
Jan 14, 2010, 3:26:29 PM1/14/10
to ImageJX, wil...@ieee.org, Johannes....@gmx.de
Johannes,

thank you for the concrete proposal. Here are my ad hoc comments:

Let me first state that simplicity has been one of ImageJ's most
attractive features and plugins in particular should be simple to
write and understand. Plugins should also be safe, i.e., the
possibility of runtime errors should be minimized. This is why I
prefer to make efficient use of Java's type and compile-time safety
wherever possible.

For the plugin architecture I see three main issues that need to be
resolved:

1) The plugin (class) must have some way of informing the execution
system about its capabilities, in particular about the objects it can
accept and handle. Currently this is accomplished by the setup method
returning a binary flag vector, but this has limitations and does not
prohibit that the plugin's run method is invoked with the wrong type
of argument. My own proposal used a set of interfaces to describe
these capabilities in a way that provided compile-time safety.
Annotations, as proposed, are another way to go. Possibly the executer
should be able to check theses capabilities without actually
instantiating the plugin. This would, for example, facilitate context-
dependent menus where plugin entries are deactivated (grayed) if the
current image is not of proper type.

2) It should be possible to parameterize a plugin. This could again be
done in several ways. One option is to explicitly set some plugin
fields before invoking the run method. A second way is to pass a list
(map) of parameters to the run method, as you proposed. Another option
would be to pass a specific configuration object, possibly created by
a suitable static factory method of the plugin class (this is probably
my favorite approach).
What I see critical is the way how input arguments are passed to the
plugin. Passing an array of "Object"s and using "String"s to identify
their meaning (possibly emulating those elegant keyword parameters we
remember from Common Lisp) creates many possibilities for runtime
errors, unless you add extensive name and type checking code inside
the plugin, I assume. Could symbols (defined inside the plugin class)
be used as identifiers instead?

3) A plugin should be able to return results for subsequent use and
not only work by side effect. Again this could be done by returning a
map of key/value pairs or by reading some result fields after the
plugin terminates.

One thing to be careful about in this context are side effects of
class reloading. Assume you define a result class R in your plugin
file (or in a separate file), then apply your plugin, which creates a
result object r (an instance of R) for later use. Next you (edit and)
reload your plugin, which presumably also reloads class R. In this
case, the existing result object r is not an instance of R any longer.
This keeps me from using self-defined data types to define
intermediate results. Perhaps someone has an idea if this can be
resolved at all.

--Wilhelm


On 12 Jan., 17:29, Johannes Schindelin <Johannes.Schinde...@gmx.de>
wrote:


> [merged two threads from two mailing lists...]
>
> On Sat, 26 Dec 2009, Johannes Schindelin wrote:
> > I have been thinking about an annotation-based approach for some time
> > now, but due to other obligations, did not have time to implement
> > anything concretely yet.  Hopefully that will change in a few weeks
> > (when I can get sidetracked from working on proper documentation on the
> > image library by the plugins interface :-)
>
> Okay, so here it goes...
>
> I described the plugin architecture in a rather hand-waving manner before
> Christmas, so I thought I should put my money where my mouth is and
> demonstrate that it actually works.
>
> This patch series was developed inside fiji.git, as this framework is what
> I am most familiar with.  You can see it in gitweb here (the latest 3

> patches):http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=shortlog;h=...

Johannes Schindelin

unread,
Jan 18, 2010, 4:44:30 AM1/18/10
to Wilhelm Burger, ImageJX
Hi,

On Thu, 14 Jan 2010, Wilhelm Burger wrote:

> Let me first state that simplicity has been one of ImageJ's most
> attractive features and plugins in particular should be simple to
> write and understand. Plugins should also be safe, i.e., the
> possibility of runtime errors should be minimized. This is why I
> prefer to make efficient use of Java's type and compile-time safety
> wherever possible.

Well, I agree on the simplicity of the plugins, but I think that some
things cannot be done safely if you want to have a simple plugin
architecture.

Note e.g. this example:

@Parameter(type = "8-bit")
ImagePlus image;

It cannot get much simpler than that. You do not even have to remember
that there is a special class of PlugIn, namely PlugInFilter (which is in
a different package, though), which is supposed to run on single images.
You do not need a special interface for algorithms requiring, say, 17
input images.

You also do not have to extend ImageJ with a new interface whenever you
introduce a new image type. (Which would provide a little compile-time
safety, being not as strong as other compile-time safety valves, though,
because you can literally guarantee that some ImageJ versions will not run
the plugin due to a lack of said interface.)

So while the approach with new interfaces sounds attractive from a design
point of view, I think that Saint-Exup�ry would strip it down to something
similarly compact as the annotation approach I presented.

> For the plugin architecture I see three main issues that need to be
> resolved:
>
> 1) The plugin (class) must have some way of informing the execution
> system about its capabilities, in particular about the objects it can
> accept and handle. Currently this is accomplished by the setup
> method returning a binary flag vector, but this has limitations and
> does not prohibit that the plugin's run method is invoked with the
> wrong type of argument. My own proposal used a set of interfaces to
> describe these capabilities in a way that provided compile-time
> safety. Annotations, as proposed, are another way to go. Possibly
> the executer should be able to check theses capabilities without
> actually instantiating the plugin. This would, for example,
> facilitate context- dependent menus where plugin entries are
> deactivated (grayed) if the current image is not of proper type.

Indeed. As you did not say it, I will: annotations can do that, too.

> 2) It should be possible to parameterize a plugin. This could again be
> done in several ways. One option is to explicitly set some plugin
> fields before invoking the run method. A second way is to pass a list
> (map) of parameters to the run method, as you proposed. Another
> option would be to pass a specific configuration object, possibly
> created by a suitable static factory method of the plugin class (this
> is probably my favorite approach).

The first way was suggested by me, too. I would like all @Parameter
fields to be public, and I would like to suggest adding constructors
setting all the parameters, so that you _can_ have compile-time safety.

I do not like the way with the configuration object, because it requires a
factory, making things more complicated than an average
biologist-wanting-to-turn-half-programmer can grasp in half an hour.
_This_ is the time you'll have to beat.

Besides, configuration objects give you a false sense of type-safety: it
is all too easy to add a vital option in a certain version of the plugin
which callers do not know about, and which they consequently forget to
set.

> What I see critical is the way how input arguments are passed to the
> plugin. Passing an array of "Object"s and using "String"s to identify
> their meaning (possibly emulating those elegant keyword parameters we
> remember from Common Lisp) creates many possibilities for runtime
> errors, unless you add extensive name and type checking code inside the
> plugin, I assume.

Yes. This is a concious trade-off between ease-of-use and type-safety: if
you want to program quickly and be done with it, use the run() method. If
you are concerned about type-safety, instantiate the plugin and set the
fields explicitely.

> Could symbols (defined inside the plugin class) be used as identifiers
> instead?

Symbols could just as well get out-of-sync.

> 3) A plugin should be able to return results for subsequent use and not
> only work by side effect. Again this could be done by returning a map
> of key/value pairs or by reading some result fields after the plugin
> terminates.

An object is nothing else than a bag of things with syntactic sugar on top
of it.

One piece of that sugar is that the contents are type-safe. So what I do
is asking the programmer to specify the output parameters thusly:

@Parameter(output = true)
ImagePlus output;

... which is way type-safer than the map you suggested!

In effect, the safest way to run a plugin (from Java, rather than from
clicking on a menu item!) is:

Burger_Flipper plugin = new Burger_Flipper();
plugin.input = ...
plugin.flipVelocity = ...
plugin.run();
processFurther(plugin.output);

... which boils down to the second thing you suggested, and which just so
happens to be part of the architecture I proposed.

> One thing to be careful about in this context are side effects of class
> reloading.

Now, now. Class reloading should only be necessary when you have a new
version of a plugin, in which case you should not use instances of the old
plugin anymore.

Class reloading should not be part of your plugin design, as there are
most certainly simpler ways to achieve the goals behind said design!

> Assume you define a result class R in your plugin file (or in
> a separate file), then apply your plugin, which creates a result object
> r (an instance of R) for later use. Next you (edit and) reload your
> plugin, which presumably also reloads class R. In this case, the
> existing result object r is not an instance of R any longer. This keeps
> me from using self-defined data types to define intermediate results.

Self-defined data types must be either compile-time, in which case I do
not see a problem, or at run-time, in which case I must contest their use
due to lack of compile-time checks.

Ciao,
Johannes

Dimiter Prodanov

unread,
Jan 18, 2010, 9:15:37 AM1/18/10
to ima...@googlegroups.com, Wilhelm Burger
Hi,
few comments from me as well:

I try to implement the bean pattern as much as possible. So I don't
like the idea of public input and output fields.
On the other hand, now I see the utility of statements like:

> @Parameter(input= true)
> ImagePlus input;

> @Parameter(output = true)
> ImagePlus output;

In any case I would argue also to keep also the map approach. Life is
so much easier since I started using Maps and Collections and the
likes from the Java zoo ;).

best regards,

Dimiter

Johannes Schindelin

unread,
Jan 18, 2010, 9:28:25 AM1/18/10
to Dimiter Prodanov, ima...@googlegroups.com, Wilhelm Burger
Hi,

On Mon, 18 Jan 2010, Dimiter Prodanov wrote:

> few comments from me as well:
>
> I try to implement the bean pattern as much as possible. So I don't
> like the idea of public input and output fields.

The problem I see with the bean pattern is that it is a substantial
increase of code (which invariably reduces readability), and it is
throwing people off who just want to implement their algorithm and be done
with it (which current ImageJ's interfaces accomplish very, very nicely,
and which I deem is one of the most important reasons for ImageJ's number
of plugins).

> On the other hand, now I see the utility of statements like:
>
> > @Parameter(input= true)
> > ImagePlus input;
>
> > @Parameter(output = true)
> > ImagePlus output;
>
> In any case I would argue also to keep also the map approach. Life is so
> much easier since I started using Maps and Collections and the likes
> from the Java zoo ;).

I fully agree. The idea was also to require implementation of an
interface only, and provide the functions as static methods of a helper
class (PlugInFunctions in my case).

That way, your implementation of a plugin can be very, very simple, yet
you can use the same functions on it as if you would have extended the
abstract class (which does nothing else than calling into the static
methods of PlugInFunctions).

Ciao,
Johannes

Reply all
Reply to author
Forward
0 new messages