Trying to get a groovy script to work

imagej

#1

I am still trying to get a meaningful oops script to run.
I finally have a groovy script which runs properly in the script editor:

#@ ImagePlus imp
#@ ImageJ ij
#@ OpService ops
#@ CommandService cmd
#@ UIService ui

//ect1 = ij.io().open("/home/ilan/Documents/ect1/ECT1.dcm")
meta = getMeta(1)
println meta

def getMeta(int slice) {
if( imp.getImage() == null) return null;
String meta = imp.getStack().getSliceLabel(slice);
// meta will be null for SPECT studies
if (meta == null ) meta = imp.getProperty("Info");
return meta
}

I can bring up my PET data, run the above in the script editor and see the dicom tags.
The next step is to activate it from inside my java code. I would like to input to the script which image I want to use, since essentially always there are multiple images open.

Looking around I found examples of activating scripts from inside java. This one also works (although it puts out 10 pages of output in the console, so I wouldn’t say it is working properly).

double runScript() {
	try {
		final Context context = new Context(ScriptService.class);
		final ScriptService scriptService = context.getService(ScriptService.class);
		final String script = "as.integer(1) + as.integer(2)";
		final ScriptModule m = scriptService.run("add.r", script, true).get();
		final Object result = m.getReturnValue();
		String res1 = result.toString();
	} catch (Exception e) { ChoosePetCt.stackTrace2Log(e);}
	return 0;
}

I can catch it with a debugger at result.toString and see it has an array list with 1 entry of value 3.
1+2=3.
I have no idea how I would guess to use “add.r”. Another example with “add.kt” with a script of “1 + 2”
completely crashes. I would guess it doesn’t know what “add.kt” means. Neither do I.

My guess is that I should use something like
final ScriptModule m = scriptService.run(“myScript.groovy”, myect, true).get();
The script has the file name of myScript.groovy. Where would I locate this file?
Inside Fiji.app->macros? Somewhere else? Can I put more than just a single input parameter?


#2

Hello Ilan -

I don’t have an answer to your question of how to call a groovy script
from java. But I suggest the obvious alternative of using java, below.

Why not simply program the retrieval of the metadata in java?

You say you wish to activate the groovy script “from inside my java
code.” So I’m imagining that you are writing some larger processing
in java and are comfortable with some java programming. (You also
post a java snippet.)

Your getMeta code looks quite straightforward. I would be inclined
to program it in-line where it’s need in your java processing. (Or you
could package it as a java free function – oops, I meant to say a java
class with a single static method – for reuse in multiple places.)

Groovy is java-based, so the translation should be trivial.

If this doesn’t make sense for your use case, could you add some more
detail on why you want to throw groovy into the mix?

Thanks, mm


#3

Thanks for your help. I am taking the simplest thing possible as a first step. In fact the code myScript.groovy is taken from my java code. I finally learned that I need to use “def” instead of “String” which I would use in java to indicate the type of returned object.
My idea is start with the simplest example and go on from there. In oops I discovered that DefaultSpherecity can’t work under pure java, and can only work under a script. For me java is 10+ times easier, but I need to check out using real scripts. Groovy is the language which was recommended to me. If it is the easiest in some sense, that is a good enough reason for me.

So I am plugging along trying to make something which actually works. First the simplest example (which needs to include input parameters, i.e which image I want and maybe some other things as well). At least groovy now seems under control, but interfacing with it, isn’t yet under my control.

You mention I need to post a java snippet. Isn’t double runScript() a java snippet? That is what I am trying to run in my java program. It doesn’t yet have any input parameters which is a serious flaw, but I was willing to take anything as step zero. I still have to figure out how I tell the script how to use one very specific image, not just a random choice from what happens to be in memory.

Thanks again,
Ilan


#4

Hello Ilan -

First, let me apologize for being unclear. When I wrote “You also
post a java snippet,” I didn’t mean that you hadn’t posted any java,
but, rather, that the double runScript() that you posted is java,
indicating to me that you have some familiarity with java.

I believe that you should be able to access and run DefaultSphericity
from java, just like (almost) everything in ImageJ. Here is a complete
IJ1-style java plugin that calls Ops Sphericity on a (vacuous) Mesh:

import ij.IJ;
import ij.plugin.PlugIn;

import net.imagej.legacy.IJ1Helper;
import net.imagej.ops.OpService;

import net.imagej.mesh.Mesh;
import net.imagej.mesh.naive.NaiveDoubleMesh;

import net.imglib2.type.numeric.real.DoubleType;

public class My_Plugin implements PlugIn {
  public void run (String arg) {
    IJ.log ("run ops sphericity from java ...");

    // some boiler plate to get Ops ...
    OpService ops = IJ1Helper.getLegacyContext().getService(OpService.class);

    Mesh m = new NaiveDoubleMesh();  // empty mesh
    DoubleType dbl = ops.geom().sphericity (m);  // "Nan" for the result
    IJ.log ("dbl = " + dbl);
  }
}

I compile this against the jars that ship with a stock Fiji install, and
copy the jar to the Fiji plugins folder. I then run it from Fiji:
Plugins > My Plugin.

For what I imagine that you’re doing, I would suggest just using
java (especially if it’s “10+ times easier”). If you’re doing your
heavy lifting in scripts (and like it), then use scripts. If you want
to run some quick, short script from the script editor (e.g., to
avoid compiling and packaging some java code), use a script.

But if you’re doing your heavy lifting in java, it would just seem
to complicate things to carve out some “subroutine” into a script,
and then jump though hoops calling into the scripting language
from java. Just do it straight in java (especially since (almost) all
of ImageJ is written in java, so no extra “translation” is needed).

Thanks, mm


#5

Thanks mm. I wanted to take your suggestions seriously by trying to implement them in my code. I have 2 environments: ImageJ in source code which I don’t update very often and Fiji in the very latest state where I attach a debugger (when needed).

Your suggestions I decided to try under Fiji, with debugger, and my code wouldn’t work. ImageJ had no problem, at least with what I already had written.
I noticed you had used NaiveDoubleMesh() whereas I was using DefaultMesh(points). DefaultMesh had a nice advantage that I could put in the vertices at the constructor.

Finally I found the problem with the Fiji was my use of DefaultMesh which wasn’t even listed in the class overview. NaiveDoubleMesh is listed but with an empty constructor. I have a list of vertices, but it isn’t clear how to set them. With DefaultMesh there is a method to set the facets as well, if I need to do so.

In short there seem to be 2 different worlds: the one I have been playing with which includes DefaultMesh and another one where what I “knew” no longer exists. To make some progress, I need an actual mesh in the new world.

Thanks,
Ilan

P.S. I see what to do. Instead of building a list, I need to add the points directly to the mesh instead of to the list.


#6

Hello mm,
I really need some addition help. You use

OpService ops = IJ1Helper.getLegacyContext().getService(OpService.class);

Mesh m = new NaiveDoubleMesh();  // empty mesh
DoubleType dbl = ops.geom().sphericity (m);  // "Nan" for the result

I made a real NaiveDoubleMesh and added vertices to it.
Then I used what should be a variation on your code

Context context = new Context(OpService.class);
OpService ops = context.getService(OpService.class);
DoubleType dbl = ops.geom().sphericity (mesh0);

Where mesh0 is my actual NaiveDoubleMesh. I get a compilation error
that NaiveDoubleMesh cannot be converted to Mesh.
I don’t understand why you didn’t get the same error.
It makes sense that I can’t just cast NaiveDoubleMesh as Mesh.
How do you attack the problem?

Thanks,
Ilan


#7

Hi mm,

I tracked the problem down to imports.
First of all, I verified that the problem is not connected to the boiler plate as the error message is the same. The code is now

	OpService ops = IJ1Helper.getLegacyContext().getService(OpService.class);
//		Context context = new Context(OpService.class);
//		OpService ops = context.getService(OpService.class);
	Mesh m0 = new NaiveDoubleMesh();
	DoubleType dbl = ops.geom().sphericity (m0);

The 2 variations of the import are
import net.imagej.mesh.Mesh; and
import net.imagej.ops.geom.geom3d.mesh.Mesh;

The first says: incompatible types net.imagej.mesh.Mesh cannot be converted to imagej.ops.geom.geom3d.mesh.Mesh
The second says: incompatible types NaiveDoubleMesh cannot be converted to Mesh

So I can control which line has the error by choosing the import value, but I can’t get it to be without error. There is no import for sphericity(). Are there 2 different versions involved so that it is picking up the wrong one? For NaiveDoubleMesh, I want net.imagej.mesh.Mesh but sphericity() wants net.imagej.ops.geom.geom3d.mesh.Mesh.

Is this Catch 22, or can I somehow tell it to use a different sphericity()?
Thanks,
Ilan

P.S. There is a problem with slf4j:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/ilan/Fiji.app/jars/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/ilan/Fiji.app/jars/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

I checked logback-classic-1.2.3.jar and slf4j-simple-1.7.25.jar and there is indeed duplication of code. Something is clearly wrong here, and one of these jars should be removed. I don’t know which one.
I have my doubts that this is the cause of my problem, but I would like to clean up this mess.

Can anyone give a hint as which one to eliminate?


#8

Hello mountain man,
I thought there could be some magic which I am missing. Maybe your plugin, actually needs to be a real plugin? Hard to believe, but it is something I can check.

So I made a new plugin using your code. In my plugin I get the same error:
incompatible types net.imagej.mesh.Mesh cannot be converted to imagej.ops.geom.geom3d.mesh.Mesh

I looked at the code
Interface Ops.Geometric.Sphericity is an interface where the only known implementing class is DefaultSphericity. That sits under net.imagej.ops.geom.geom3d.DefaultSphericity which gives a strong correlation to the error message.

You said that you actually compiled the code and ran it, so why does it refuse to even compile for me? The error message seems to make perfect sense, so I don’t know what to think. Is there some magic I am missing?

Thanks,
Ilan


#9

Hello Ilan -

TLDR:

You are probably mixing the new Mesh with the old Sphericity.
If so, you will need to dig into your ImageJ and clean it up to get
a self-consistent version.

(Details below.)

Details:

I believe that your problem with Mesh stems from mixing pieces
of two incompatible versions together.

net.imagej.mesh.Mesh works for me, while
net.imagej.ops.geom.geom3d.mesh.Mesh doesn’t exist
(for me, anymore). See some details, below.

The example I posted compiles just fine for me. But if I add
the line:

import net.imagej.ops.geom.geom3d.mesh.Mesh;

I get the compile-time error:

My_Plugin.java:11: error: cannot find symbol
import net.imagej.ops.geom.geom3d.mesh.Mesh;
                                      ^
  symbol:   class Mesh
  location: package net.imagej.ops.geom.geom3d.mesh

Note, I can compile the line:

import net.imagej.ops.geom.geom3d.mesh.Vertex;

so the package is there, but Mesh has been removed from it.

Here is my specific compile command:

javac -source 1.8 -target 1.8 -g -cp .:<path_to_Fiji.app>/Fiji.app/jars/* My_Plugin.java

I am using only the ImageJ jars from a stock Fiji install that I
downloaded some months ago and routinely let auto-update itself.
My current (auto-updated) version is:

ImageJ 2.0.0-rc-68/1.52e; Java 1.8.0_66 [64-bit];

running on 64-bit ubuntu 16.04 LTS

As for the error you get when you call ops.geom().sphericity (m0)
with m0 a net.imagej.mesh.Mesh, I bet you are somehow still
using the old version of Sphericity that is expecting the old
version of Mesh (i.e., net.imagej.ops.geom.geom3d.mesh.Mesh).

(Note, I can’t reproduce this problem, because I believe that I
am using the new version of Sphericity.)

And, the other way around, you can’t convert a NaiveDoubleMesh
to the old net.imagej.ops.geom.geom3d.mesh.Mesh, because
NaiveDoubleMesh implements only the new
net.imagej.mesh.Mesh

Could you post a small, complete, trimmed-down example in pure
java that illustrates your problem? Please also post the compile
command you use, and the full set of compile errors you get.

If you can somehow show what jars you are compiling against, and
somehow the jar versions or dates, that might help clear up whether
or not you are somehow mixing old and new.

Lastly, you could try setting up a sandbox, download a fresh
copy of the stock Fiji install, and try compiling my example
against only the Fiji jars. Doing that really should work.

Some detail on the removal of net.imagej.ops.geom.geom3d.mesh.Mesh:

I recall seeing some time ago that the interface
net.imagej.ops.geom.geom3d.mesh.Mesh had been
deprecated, but I can’t find the specific citation. In any event,
in the github source for that package:

https://github.com/imagej/imagej-ops/tree/master/src/main/java/net/imagej/ops/geom/geom3d/mesh/

you can find the following commit:

https://github.com/imagej/imagej-ops/commit/78ff2373ccc3db101104d279940d15ee3a94318e#diff-f3d4650088c5fa1d1d18ea8d02aa6fb9

that says:

Migrate to the new imagej-mesh API

We no longer need net.imagej.ops.geom.geom3d.mesh.Mesh,
but rather the Mesh interfaces from imagej-mesh 0.4.0.

(I would expect that Curtis could give you the history on this,
and correct any misinformation I have likely put forth.)

Thanks, mm.


#10

Thanks mm,
Yes, I have been fighting many battles because of a mess in jar files.
I updated ALL jar files to the latest versions and now things compile, at last.
I probably would have had problems with my battles with the scripts as well.

Now I can return to java which I know and love and see where the real problems are.
The joke is that in my own code I accept zero warnings, but external code is not under my control. Murphy’s law worked very well where the problem comes from a direction you didn’t suspect.

Now I will start to work again.
Thanks for your help,
Ilan


#11

Hello mm,
After cleaning up the mess, things are working.

Since you obviously have experience with these things, I want to ask you some general questions. The code is now

//		OpService ops = IJ1Helper.getLegacyContext().getService(OpService.class);
	Context context = new Context(OpService.class);
	OpService ops = context.getService(OpService.class);
	DoubleType dbl = ops.geom().sphericity (mesh0);

	DefaultVolumeMesh mesh1 = new DefaultVolumeMesh();
	vol1 = mesh1.calculate(mesh0);
	double vol = vol1.get();

	DefaultSurfaceArea dsa1 = new DefaultSurfaceArea();
	surface = dsa1.createOutput(mesh0);
	dsa1.compute(mesh0, surface);

The first part is yours DoubleType dbl = ops.geom().sphericity (mesh0);
You use ops.geom().sphericity with a mesh. This is presumably the proper way to do things. This is an interface and I vaguely remember that the interface told you to use a mesh.

Just to see if I could duplicate the result, I wanted to calculate the DefaultVolumeMesh and the DefaultSurfaceArea. I don’t know how to play the same game with ops.geom()… so I called the functions directly.
In your case your didn’t call DefaultSphericity but rather the interface sphericity. I couldn’t find anything similar for DefaultVolumeMesh or DefaultSurfaceArea.

I see in https://github.com/imagej/imagej-ops/blob/master/src/main/java/net/imagej/ops/geom/GeomNamespace.java that DefaultSurfaceArea really calls net.imagej.ops.Ops.Geometric.BoundarySize, so maybe this is what I should be calling? Presumably it also has an input of mesh.

In short is there some starting point where I could make a reasonable guess as to what is the desired object to call, and its parameters? I don’t have anything concrete in mind at the moment, but I would like to know where to start when I do have something else to try.

Thanks for your help,
Ilan


#12

Hi Ilan -

First, it might make sense to start a new thread with a new post.
(This is getting pretty far afield from groovy.)

Some general comments, below.

I don’t understand this very well. What I think is going on is that
IJ2 has this “ops framework” and a core piece of it is the “ops
matcher.” I think when you make a call like ops.geom(), you
are entering the ops-matcher world which somehow (maybe
concrete ops register their capabilities somehow? maybe through
reflection?) finds for you the concrete implementation of your
requested op that best matches your use case.

In the case we were playing with – sphericity() – the only
concrete implementation (that ships with stock Fiji) is
DefaultSphericity, So when you call
ops.geom().sphericity(), the ops-matcher somehow
translates that to a call to DefaultSphericity.compute()
(or whatever the actual do-the-real-work function is).

As I understand it, these ops are designed to be called (by end
users) through the ops matcher. That doesn’t mean you can’t
instantiate a concrete op yourself and call its compute() method,
but it might be more roundabout and confusing to do it that way.

I’ve only run ops by using the ops.geom().sphericity()
approach.

I don’t know of any good documentation for this. I’ve generally
looked at tutorials to get some sample code snippets for
suggestions of what might be possible, and then blundered
around the javadoc and source code to flesh out more of the
details.

Your best bet would probably be to post specific questions you
have in new threads – tag them imagej-ops or something
similar – and then hope you can get Curtis or one of the other
Ops-Meisters to give you a better-informed answer than I would
be able to manage.

Thanks, mm