morphonets / SNT

The ImageJ framework for quantification of neuronal anatomy
https://imagej.net/plugins/snt
GNU General Public License v3.0
42 stars 18 forks source link

Tubeness Op throws NullPointerException #173

Closed gselzer closed 1 year ago

gselzer commented 1 year ago

Describe the bug Running the Tubeness Op throws a NullPointerException when run with ops.run. I originally saw this running in napari-imagej

To Reproduce

  1. Ensure Neuroanatomy is enabled within Fiji
  2. Open the Script Editor, and paste the following groovy script in:
    
    #@ Img input
    #@ output Img out
    #@ OpService ops

out = ops.create().img(input) spacing = [2.0, 2.0] scale = [2.0, 2.0] numThreads = 2

ops.run("filter.tubeness", out, input, spacing, scale, numThreads)


3. Click `Run`
4. See error

Note that when I run in napari-imagej, I actually see a more detailed error:

[java.lang.Enum.toString] [ERROR] Command errored: filter.tubeness java.lang.NullPointerException at net.imagej.ops.DefaultOpMatchingService.createModule(DefaultOpMatchingService.java:571) at net.imagej.ops.DefaultOpMatchingService.moduleConforms(DefaultOpMatchingService.java:486) at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:292) at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:137) at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:94) at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:82) at net.imagej.ops.OpEnvironment.module(OpEnvironment.java:253) at net.imagej.ops.OpEnvironment.run(OpEnvironment.java:136) at net.imagej.ops.OpListingModule.run(OpListingModule.java:68) at org.scijava.module.ModuleRunner.run(ModuleRunner.java:165) at org.scijava.module.ModuleRunner.call(ModuleRunner.java:125) at org.scijava.module.ModuleRunner.call(ModuleRunner.java:64) at org.scijava.thread.DefaultThreadService.lambda$wrap$2(DefaultThreadService.java:247) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:750) [ERROR] Cannot create module: sc.fiji.snt.filter.Tubeness org.scijava.module.ModuleException: org.scijava.InstantiableException: java.lang.InstantiationException: sc.fiji.snt.filter.Tubeness at org.scijava.command.CommandModule.instantiateCommand(CommandModule.java:251) at org.scijava.command.CommandModule.(CommandModule.java:98) at org.scijava.command.CommandInfo.createModule(CommandInfo.java:326) at org.scijava.module.DefaultModuleService.createModule(DefaultModuleService.java:167) at net.imagej.ops.DefaultOpMatchingService.createModule(DefaultOpMatchingService.java:565) at net.imagej.ops.DefaultOpMatchingService.moduleConforms(DefaultOpMatchingService.java:486) at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:292) at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:137) at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:94) at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:82) at net.imagej.ops.OpEnvironment.module(OpEnvironment.java:253) at net.imagej.ops.OpEnvironment.run(OpEnvironment.java:136) at net.imagej.ops.OpListingModule.run(OpListingModule.java:68) at org.scijava.module.ModuleRunner.run(ModuleRunner.java:165) at org.scijava.module.ModuleRunner.call(ModuleRunner.java:125) at org.scijava.module.ModuleRunner.call(ModuleRunner.java:64) at org.scijava.thread.DefaultThreadService.lambda$wrap$2(DefaultThreadService.java:247) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:750) Caused by: org.scijava.InstantiableException: java.lang.InstantiationException: sc.fiji.snt.filter.Tubeness at org.scijava.plugin.PluginInfo.createInstance(PluginInfo.java:309) at org.scijava.command.CommandInfo.createInstance(CommandInfo.java:248) at org.scijava.command.CommandModule.instantiateCommand(CommandModule.java:248) ... 20 more Caused by: java.lang.InstantiationException: sc.fiji.snt.filter.Tubeness at java.lang.Class.newInstance(Class.java:427) at org.scijava.plugin.PluginInfo.createInstance(PluginInfo.java:304) ... 22 more Caused by: java.lang.NoSuchMethodException: sc.fiji.snt.filter.Tubeness.() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.newInstance(Class.java:412) ... 23 more



**Expected behavior**

The Op should run without error

**Screenshots**

![image](https://github.com/morphonets/SNT/assets/29754838/205f4608-4508-405b-bf48-499246d654d0)

**Additional context**
<!-- Add any other context about the problem here. -->

**Fiji details:**
(Output too long, can provide via Gist if it would be helpful)
Running Fiji 2.9.0/1.53t, Java 1.8.0_322
tferr commented 1 year ago

Thanks for the report! @carshadi, do you have time to look into it?

tferr commented 1 year ago

@gselzer, actually I don't understand why this happens. If I understand correctly DefaultOpMatchingService finds this OP, but fails to create a Command, presumably because OpCandidate#OpInfo() is null!? Is this correct? How can we have it so OpInfo is not null? How do we set it?

Also, BTW, this op (written by @carshadi) improves the original tubeness Op, by allowing multiple scales (as in the Frangi op). At the time we did not submit it to net.imagej.ops because we had not done enough testing, but now we can confirm that it works great, so maybe now that is the time!?

carshadi commented 1 year ago

According to this stack overflow post https://stackoverflow.com/a/18024061 , this can happen if the class doesn't have a default (empty) constructor.

tferr commented 1 year ago

Wow. That seems to fix it! I had never seen this before!

tferr commented 1 year ago

So adding a dummy constructor works:


    public Tubeness() {
       // do nothing
    }

However, setting it to private does not work. It needs to be public. But now if someone tries to use that constructor, the computation will fail because the spacing and scales fields are not set. What is the recommendation:

  1. make the empty constructor define the required fields using some sensible defaults
  2. Mention in the javadocs that the constructor is dummy and not to be used?

But above all, why does this happen? Funny enough the IDE suggests removing the empty constructor as it does nothing :)

carshadi commented 1 year ago

For 1) I'm not sure there will be sensible defaults since getting a decent result requires knowing the voxel size of the image. 2) makes more sense to me. I am also not too familiar with how Op parameters are populated. Does running it in this way call the empty constructor and populate the @Parameter annotated fields with the given arguments?

ops.run("filter.tubeness", out, input, spacing, scale, numThreads)
tferr commented 1 year ago

OK. I think the empty constructor is needed earlier on by the OpService, when looking up which Ops are available. The fix is now in place with 47a319d