Full Path Requirement for new input parameter types

We have recently created a new input parameter type by implementing a new InputWidget in our java written plugins. The new data type is simply an extension of net.imagej.table.AbstractTable called SDMMResultsTable in which we have added some useful utility methods. The InputWidget works great, however, we found that it only works if we provide the full class path for the type. Just providing the class name alone doesn’t work. So far we have only tested this in groovy. Is there a way to resolve this issue so we can just write the class name?

So, for example, if we write a groovy script that retrieves the name of the table (which we have added in our custom implementation):

import de.mpg.biochem.sdmm.table.SDMMResultsTable

#@ SDMMResultsTable table
#@OUTPUT String name

name = table.getName()

We get the following error message:

[WARNING] Invalid class: SDMMResultsTable
javax.script.ScriptException: Unknown type: SDMMResultsTable
	at org.scijava.script.DefaultScriptService.lookupClass(DefaultScriptService.java:216)
	at org.scijava.script.process.ParameterScriptProcessor.parseParam(ParameterScriptProcessor.java:213)
	at org.scijava.script.process.ParameterScriptProcessor.parseParam(ParameterScriptProcessor.java:178)
	at org.scijava.script.process.ParameterScriptProcessor.process(ParameterScriptProcessor.java:169)
	at org.scijava.script.process.ParameterScriptProcessor.process(ParameterScriptProcessor.java:138)
	at org.scijava.script.process.ScriptProcessorService.process(ScriptProcessorService.java:79)
	at org.scijava.script.ScriptInfo.parseParameters(ScriptInfo.java:293)
	at org.scijava.module.AbstractModuleInfo.initParameters(AbstractModuleInfo.java:192)
	at org.scijava.module.AbstractModuleInfo.inputList(AbstractModuleInfo.java:154)
	at org.scijava.module.AbstractModuleInfo.inputs(AbstractModuleInfo.java:96)
	at org.scijava.module.process.GatewayPreprocessor.process(GatewayPreprocessor.java:66)
	at org.scijava.module.ModuleRunner.preProcess(ModuleRunner.java:105)
	at org.scijava.module.ModuleRunner.run(ModuleRunner.java:157)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:127)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:66)
	at org.scijava.thread.DefaultThreadService$3.call(DefaultThreadService.java:238)
	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:748)
Caused by: java.lang.IllegalArgumentException: Cannot load class: SDMMResultsTable
	at org.scijava.util.Types.iae(Types.java:959)
	at org.scijava.util.Types.load(Types.java:218)
	at org.scijava.util.Types.load(Types.java:139)
	at org.scijava.script.DefaultScriptService.lookupClass(DefaultScriptService.java:211)
	... 19 more
Caused by: java.lang.ClassNotFoundException: SDMMResultsTable
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at org.scijava.util.Types.load(Types.java:210)
	... 21 more
[WARNING] Ignoring invalid parameter:  SDMMResultsTable table

However, the following runs with no errors and our custom InputWidget is called up when generating the input dialog:

#@ de.mpg.biochem.sdmm.table.SDMMResultsTable table
#@OUTPUT String name

name = table.getName()

The result of this script is a text window with the table name.

I looked around a little but I couldn’t find a similar post and it wasn’t immediately clear to me how to fix this problem. Interestingly, our custom ResultsTableService which is in the same package as SDMMResultsTable can be used as an input parameter without the full path. I assume this is because of how Services are loaded at runtime. I wonder then if there is a custom plugin type we should use for our table to make it visible to the class lookup process.

Would you be so kind as to link to the source code of your input widget, so that others can learn from it as well?

Yes, by default, script parameters of any type work with the fully qualified class path only. If you want to define default behavior for a shorter alias of the class, you’ll need to use ScriptService#addAlias (usually in the initialize() method of a Service), as illustrated e.g. here:

You should however make sure that the alias is unique and will also be when used in combination with other components, so this should be handled with care.


I’m curious what utility methods you added, and why you required them.

Note that the net.imagej.table.* classes will soon be deprecated and replaced by org.scijava.table.* from the scijava-table repository. Together with this transition, most likely we can also introduce a generic @# Table input parameter.
When creating your own useful data types and widgets, please consider if there is any functionality that can be of more general use than just to your plugin(s), and if so, please contribute these improvements upstream into the respective SciJava repositories, so that other projects can benefit from them as well, and we avoid cluttering the ecosystem with many custom-made solutions :slight_smile:.

1 Like

Thanks @imagejan for the very fast response and helpful info! As you suggested adding the Alias to the script service in the initialize method of the service worked prefectly.

The input widget doesn’t do anything special except present a JCombBox with a list of available tables managed by the service. The code is basically just a copy of

But modified to work with our service. Here is the source:

package de.mpg.biochem.sdmm.table;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JPanel;

import org.scijava.log.LogService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.widget.*;
import org.scijava.widget.WidgetModel;

import org.scijava.ui.swing.widget.SwingInputWidget;

/**
 * Swing implementation of multiple choice selector widget for SDMMResutlsTable selection.
 * 
 * @author Karl Duderstadt
 */
@Plugin(type = InputWidget.class)
public class SDMMTableWidget extends SwingInputWidget<SDMMResultsTable> implements InputWidget<SDMMResultsTable, JPanel>, ActionListener {

	@Parameter
    private ResultsTableService resultsTableService;
	
	@Parameter
	private LogService logService;	
		
	private JComboBox<Object> comboBox;

	@Override
	public void actionPerformed(final ActionEvent e) {
		updateModel();
	}

	// -- InputWidget methods --

	@Override
	public SDMMResultsTable getValue() {
		return resultsTableService.getResultsTable((String)comboBox.getSelectedItem());
	}

	// -- WrapperPlugin methods --

	@Override
	public void set(final WidgetModel model) {
		super.set(model);
		
		String[] items = new String[resultsTableService.getTableNames().size()];
		resultsTableService.getTableNames().toArray(items);

		comboBox = new JComboBox<>(items);
		setToolTip(comboBox);
		getComponent().add(comboBox);
		comboBox.addActionListener(this);

		refreshWidget();
	}

	// -- Typed methods --

	@Override
	public boolean supports(final WidgetModel model) {
		return super.supports(model) && model.isType(SDMMResultsTable.class) && resultsTableService.getTableNames().size() > 0;
	}

	// -- AbstractUIInputWidget methods ---

	@Override
	public void doRefresh() {
	//	final String value = get().getValue().toString();
	//	if (value.equals(comboBox.getSelectedItem())) return; // no change
	//	comboBox.setSelectedItem(value);
	}
}

For some reason we ran into problems using the default behaviour of the ObjectWidget. Perhaps it was not designed for our use. For example, if we had two tables open and tracked by the ObjectService and specified them as parameter inputs the ObjectWidget would generate a choice selection, but the text options were basically the full tables. Then we added toString into the SDMMResultsTable to provide just the name of the table, but it was not possible to select a single item - basically the pulldown became unresponsive. We plan to dig deeper into this once we have more time, but in the meantime the InputWidget offers us a bit more power to control things through our service. Do you have a different suggestion of how we should implement this using the ObjectWidget instead?

Thanks for the heads up that net.imagej.table will be moving. Will the basic framework change dramatically?

It would be awesome to have a generic @# Table input parameter. Our utility methods include simple operations on ranges of values in columns - mean, std, median, with options to perform the operations for one column based on a range of values in another. Also, table sorting and filtering tools. We seem to do these kinds of operations a lot and it’s handy to have them included.

Yes, we are eager try to make our work as general as possible and share broadly but at the moment we are still just trying to get to know the new API and migrating old code from IJ1.

Many thanks for your help!

1 Like

Thanks a lot for sharing your experience here, @karlduderstadt!

Currently I don’t have a better idea. I’d however also be interested in seeing a working example with ObjectWidget, as this is the intended use case of that widget.

This might be related to an issue where dropdowns become unresponsive with Img inputs (which also use ObjectWidgets) under certain circumstances:

Let’s hope this can be fixed soon.


That sounds very useful. It would be awesome if we can migrate some of this functionality into SciJava tables at some point in the future, when things have settled a little.

The intent is:

  1. You have your own type of object (say: MyThing).
  2. You add instances of MyThing to the ObjectService when they are intended to be generally available, and remove them when they are no longer so.
  3. You annotate your command or script with @Parameter MyThing myThing;.

And then these objects will be available from the dropdown.

Right: the ObjectWidget needs to stringify the objects in question, and right now it uses toString(). I didn’t see any other way without complicating things a lot more.

Yes, this is the bug that @imagejan linked. I was working on fixing it a few weeks ago, but have not had time to conclude that work yet. I will continue to post updates on the linked thread.

Be warned that the widget API will change significantly in SciJava 3. Sorry for that, but the widget framework needs to work more generally for dialog generation beyond only SciJava Modules. The good news is that we have a “struct” subsystem prototype that is lightweight and general, such that dialogs can be generated from any struct (i.e. thing with typed inputs). I expect that work to make substantial progress during the next 6 months.

Agreed.

1 Like

Thanks a bunch @ctrueden and @imagejan for all the helpful information.

@ctrueden - if the ObjectWidget worked how you described, it would be perfect for our rather simple case, so I hope there is a simply answer to the bug. I haven’t had much time to look into it, but it seems like the problem is in the doRefresh method. We had to comment that out to get our code working properly.

For completeness I thought I should include the final version we are using currently. The difference from above is an extra call to updateModel in the set function to ensure the value is set even if the combo box isn’t selected before hitting OK.

package de.mpg.biochem.sdmm.table;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JPanel;

import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.widget.*;
import org.scijava.widget.WidgetModel;

import org.scijava.ui.swing.widget.SwingInputWidget;

/**
 * Swing implementation of multiple choice selector widget for SDMMResutlsTable.
 * 
 * @author Karl Duderstadt
 */
@Plugin(type = InputWidget.class)
public class SDMMTableWidget extends SwingInputWidget<SDMMResultsTable> implements InputWidget<SDMMResultsTable, JPanel>, ActionListener {

	@Parameter
    private ResultsTableService resultsTableService;
		
	private JComboBox<Object> comboBox;

	@Override
	public void actionPerformed(final ActionEvent e) {
		updateModel();
	}

	// -- InputWidget methods --

	@Override
	public SDMMResultsTable getValue() {
		return resultsTableService.getResultsTable((String)comboBox.getSelectedItem());
	}

	// -- WrapperPlugin methods --

	@Override
	public void set(final WidgetModel model) {
		super.set(model);
		
		String[] items = new String[resultsTableService.getTableNames().size()];
		resultsTableService.getTableNames().toArray(items);

		comboBox = new JComboBox<>(items);
		setToolTip(comboBox);
		getComponent().add(comboBox);
		comboBox.addActionListener(this);
		
		updateModel();

		refreshWidget();
	}

	// -- Typed methods --

	@Override
	public boolean supports(final WidgetModel model) {
		return super.supports(model) && model.isType(SDMMResultsTable.class) && resultsTableService.getTableNames().size() > 0;
	}

	// -- AbstractUIInputWidget methods ---

	@Override
	public void doRefresh() {
		//final String value = get().getValue().toString();
		//if (value.equals(comboBox.getSelectedItem())) return; // no change
		//comboBox.setSelectedItem(value);
	}
}
2 Likes