scijava / scijava

Foundational libraries for scientific Java applications 🧱
https://scijava.org
6 stars 0 forks source link

Cannot create op from signature with raw op type #220

Open ctrueden opened 1 month ago

ctrueden commented 1 month ago
In [97]: from net.imglib2.outofbounds import OutOfBoundsFactory

In [98]: c = env.op("filter.gauss").inType(Img, Double.TYPE, OutOfBoundsFactory).outType(Img).computer()

In [99]: c
Out[99]: <java object 'org.scijava.ops.engine.matcher.impl.OpWrappers$Computer3OpWrapper$1GenericTypedComputer3'>

In [100]: c.infoTree().toString()
Out[100]: 'org.scijava.ops.image.filter.gauss.Gaussians.gaussRAISingleSigma(net.imglib2.RandomAccessibleInterval<I>,double,net.imglib2.outofbounds.OutOfBoundsFactory<I, net.imglib2.RandomAccessibleInterval<I>>,net.imglib2.RandomAccessibleInterval<O>)'

In [101]: sig = Ops.signature(c)

In [102]: sig
Out[102]: '|Info:org.scijava.ops.image.filter.gauss.Gaussians.gaussRAISingleSigma(net.imglib2.RandomAccessibleInterval<I>,double,net.imglib2.outofbounds.OutOfBoundsFactory<I, net.imglib2.RandomAccessibleInterval<I>>,net.imglib2.RandomAccessibleInterval<O>)@0-SNAPSHOT{}'

In [104]: try:
     ...:     o = env.opFromSignature(sig, Nil.of(Computers.Arity3))
     ...: except Exception as e:
     ...:     from scyjava import jstacktrace
     ...:     print(jstacktrace(e))
     ...:
java.lang.IllegalArgumentException: TODO
    at org.scijava.ops.engine.impl.DefaultOpEnvironment.opFromInfoChain(DefaultOpEnvironment.java:225)
    at org.scijava.ops.api.OpEnvironment.opFromInfoChain(OpEnvironment.java:165)
    at org.scijava.ops.api.OpEnvironment.opFromSignature(OpEnvironment.java:182)
gselzer commented 1 month ago

@ctrueden thanks for raising this issue. I've thought about it a fair bit, and did a little prototyping to try and fix this issue, and I can't come up with a solution that I like - you get drawn into casting issues so easily. Here's what I've been thinking about:

public <T> T opFromSignature(final String signature, final Class<T> rawOpType)

This option is basically what you asked for above, but a little simpler without the Nil. While this is very easy to implement, it starts to reintroduce output casting in a way we tried to avoid with SciJava Ops.

// some signature for a BiFunction<Img, double, Img>
var sig = ...
var img = ...
var sigma = 5.0;
var op = env.signature(sig, BiFunction.class); // This would return a raw BiFunction
// -- EITHER -- //
Img out1 = (Img) op.apply(img, sigma); // Requires cast
// -- OR -- //
var out2 = op.apply(img, sigma); // autocomplete only recognizes `Object` members on `out`.

Then, I figured that if we're going for simplicity, maybe we should just do this:

public Object opFromSignature(final String signature)

But, while this look slick in a scripting environment, it'd make the casting even worse in languages where it's needed:

// some signature for a BiFunction<Img, double, Img>
var sig = ...
var img = ...
var sigma = 5.0;
var op = (BiFunction<Img, double, Img>) env.signature(sig); // This would return an Object, cast required for following line
Img out1 = op.apply(img, sigma);

Since we worked so hard on the OpEnvironment methods to remove these casts from the original ImageJ Ops code, it feels like a step back, no?

What I'd propose instead, if you really want this type of behavior, is a static function that could live...anywhere, really, although I'd prefer to put it closer to the scripts if possible (i.e. some future scijava-ops-python component down the line, that also contains the gateway?). It would look more or less like:

public static Object reconstruct(final OpEnvironment env, final String signature) {
  InfoTree tree = env.treeFromSignature(signature);
  Nil<?> type = Nil.of(tree.info().opType());
  return env.opFromInfoTree(tree, type);
}

Then, in e.g. Python you could write:

sig = ...
img = ...
sigma = 5.0

out = Ops.reconstruct(env, sig).apply(img, sigma)