Can you save a DefaultGenericTable displayed with UIService?

Hello,

I am trying to stick to IJ2 things as much as possible when making new scripts. In the past, I have used ResultsTable because it is nice and easy to display and save analysis results. I decided to move to DefaultGenericTable and UIService instead. Using ui.show() does not present a menu drop down to save the table in the same way as ResultsTable’s interface. Is there an existing way to add a similar save functionality for displaying tables with UIService or is such functionality implemented somewhere else? Below is a quick Jython script showing the differences as an example.

#@ UIService ui

from net.imagej.table import DefaultGenericTable
from ij.measure import ResultsTable

# Make a ResultsTable using IJ1 things
IJ1_table = ResultsTable()
IJ1_table.setColumn("The", [])
IJ1_table.setColumn("struggle", [])
IJ1_table.incrementCounter()
IJ1_table.setLabel("New Row!", 0)
IJ1_table.show("IJ1 ResultsTable")

# Make a DefaultGenericTable using IJ2 things
IJ2_table = DefaultGenericTable()
IJ2_table.appendColumns("The", "struggle")
IJ2_table.appendRow("New Row!")

# Display the DefaultGenericTable
ui.show("DefaultGenericTable", IJ2_table) # Is there a generic way to make this easy to save?

This generates the two tables in the attached screenshot. Left is ui.show() of the DefaultGenericTable and the right is the ResultsTable.

ResultsTables

Cheers,
Andy

Saving ImageJ2 tables from the UI is currently not yet possible. See also this issue:


You should be able to save net.imagej.table.Table objects from scripts using IOService, by adding a few lines to your script:

#@ IOService io
#@ File (style="save") outFile
io.save(IJ2_table, outFile.getPath())

When I tried this (providing a .csv file name), I got a NullPointerException logged in the console, but the file was saved nevertheless.

1 Like

Thank you again, Jan.

Do you know by any chance if you can pluck the DefaultGenericTable out of a DefaultTableDisplay object? It is easy enough to fetch the display, but I can’t find a method to scrape the data out of the display to save. My thoughts were to just write a save script to save an actively displayed table, but I can’t figure out how to get more than just the display.

#@ File(style="save") save_path
#@ DisplayService ds
#@ IOService io
#@ LogService log

from net.imagej.table import DefaultTableDisplay

# Get the active window and check it is a Table
active_display = ds.getActiveDisplay(DefaultTableDisplay)

# Harvest the table (I don't see methods for doing this)
active_table = active_display.getTable()

# Save it
io.save(active_table, save_path.getPath())

If not, I will write a Service for accessing, displaying, and saving multiple DefaultGenericTables in a friendly manner. This is functionality I require frequently and usually messily hack together on a regular basis, so a cleaner way of doing that would be handy, at least for me.

Cheers :coffee:

Great, thanks for the illustrative example!

As TableDisplay is a subclass of Display<T> (with T being of type Table) which again extends List<T>, you can use its get(int index) method to retrieve the object that is displayed (which happens to be a GenericTable):

#@ DisplayService ds

from net.imagej.table import TableDisplay

# Get the active window and check it is a Table
active_display = ds.getActiveDisplay(TableDisplay)

# Harvest the table (I don't see methods for doing this)
active_table = active_display.get(0).class

  • Note that I’m using TableDisplay instead of DefaultTableDisplay to get the active display. This allows to retrieve also other implementations of this interface, if/when there are any in the future.
  • Note also that there are plans to migrate the table classes into a new component, scijava-table. Once this happens, the net.imagej.table classes will be deprecated and you’ll likely have to update your code at some point. But by the time this happens, I hope we’ll also have a script parameter alias that allows to retrieve the active table more easily, e.g.:
    #@ Table table
    

Ah, that is interesting. I would not have expected the displayed object to be contained in a list like that. Why are the display objects contained as a list? When would you have more than one object in a Display?

I played around with the io.save() functionality and have run into a new issue. If I run the following script and specify the file path to save to as save.csv in the dialog, I get an error that a compatible output format for csv can’t be found and the file is not saved.

Script

#@ File (style="save") save_path
#@ UIService ui
#@ IOService io

from net.imagej.table import DefaultGenericTable

# Make a DefaultGenericTable using IJ2 things
IJ2_table = DefaultGenericTable()
IJ2_table.appendColumns("The", "struggle")
IJ2_table.appendRow("New Row!")

# Display the DefaultGenericTable
ui.show("DefaultGenericTable", IJ2_table)
io.save(IJ2_table, save_path.getPath())

Error

[ERROR] null
io.scif.FormatException: No compatible output format found for extension: /home/andrew/Documents/CrashPad/Jython-Save/saved.csv
	at io.scif.services.DefaultFormatService.getWriterByExtension(DefaultFormatService.java:270)
	at io.scif.services.DefaultDatasetIOService.canSave(DefaultDatasetIOService.java:94)
	at io.scif.io.DatasetIOPlugin.supportsSave(DatasetIOPlugin.java:69)
	at org.scijava.io.IOPlugin.supportsSave(IOPlugin.java:76)
	at org.scijava.io.IOService.getSaver(IOService.java:66)
	at org.scijava.io.DefaultIOService.save(DefaultIOService.java:80)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:188)
	at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:206)
	at org.python.core.PyObject.__call__(PyObject.java:515)
	at org.python.core.PyObject.__call__(PyObject.java:519)
	at org.python.core.PyMethod.__call__(PyMethod.java:156)
	at org.python.pycode._pyx7.f$0(New_.py:14)
	at org.python.pycode._pyx7.call_function(New_.py)
	at org.python.core.PyTableCode.call(PyTableCode.java:171)
	at org.python.core.PyCode.call(PyCode.java:18)
	at org.python.core.Py.runCode(Py.java:1614)
	at org.python.core.__builtin__.eval(__builtin__.java:497)
	at org.python.core.__builtin__.eval(__builtin__.java:501)
	at org.python.util.PythonInterpreter.eval(PythonInterpreter.java:259)
	at org.python.jsr223.PyScriptEngine.eval(PyScriptEngine.java:57)
	at org.python.jsr223.PyScriptEngine.eval(PyScriptEngine.java:31)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
	at org.scijava.script.ScriptModule.run(ScriptModule.java:160)
	at org.scijava.module.ModuleRunner.run(ModuleRunner.java:168)
	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)

I get a similar error on trying to read a csv file using io.open(). The beginning of that error message and the script generating it are below.

Script

#@ File (style="open") open_path
#@ IOService io

opened_table = io.open(open_path.getPath())

Error

io.scif.FormatException: /home/andrew/Documents/CrashPad/Jython-Save/open_test.csv: No supported format found.
	at io.scif.services.DefaultFormatService.getFormatList(DefaultFormatService.java:350)
	at io.scif.services.DefaultFormatService.getFormat(DefaultFormatService.java:316)
	at io.scif.services.DefaultDatasetIOService.canOpen(DefaultDatasetIOService.java:82)
	at io.scif.io.DatasetIOPlugin.supportsOpen(DatasetIOPlugin.java:64)
	at org.scijava.io.IOService.getOpener(IOService.java:55)
...

In my case I am using Fiji that I freshly updated. It is running on an Ubuntu 18.04 machine. Do you get these errors as well if you run the Jython scripts?

Cheers :coffee:

Just stumbeled across the same issue with fiji 1.51i

Sorry, @AndyMan, that I didn’t reply until now. Somehow your post got buried…

The Display class is very generic, it can have more than one object. For example, the TextDisplay displays any number of Strings.

Unfortunately, current Fiji installations still lack the scijava-plugins-io-table component, that’s why you don’t have any suitable IOPlugin on your classpath and get these error messages.

It will be included in a standard ImageJ installation once this PR is merged and a new round of uploads has happened:

Hi @imagejan

I saw this while browsing for solutions. Thanks for the update on the matter.