Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TableOpener returns empty column list for header-only tables #1009

Open
imagejan opened this issue Mar 28, 2023 · 14 comments
Open

TableOpener returns empty column list for header-only tables #1009

imagejan opened this issue Mar 28, 2023 · 14 comments

Comments

@imagejan
Copy link
Contributor

imagejan commented Mar 28, 2023

When a table (default.tsv) contains only column headers and no data rows, the TableOpener.open() method returns a table with an empty column list:

Input file:

label_id	anchor_z	anchor_y	anchor_x	area	bb_min_z	bb_min_y	bb_min_x	bb_max_z	bb_max_y	bb_max_x

Output:

[]

As soon as you have a single data row, all is fine:

Input file:

label_id	anchor_z	anchor_y	anchor_x	area	bb_min_z	bb_min_y	bb_min_x	bb_max_z	bb_max_y	bb_max_x
1	0.0	3.6180000000000008	220.05479999999997	95.27525424000001	0	0	157	1	7	166

Output:

[label_id, anchor_z, anchor_y, anchor_x, area, bb_min_z, bb_min_y, bb_min_x, bb_max_z, bb_max_y, bb_max_x]

This behavior causes problems e.g. for MoBIE datasets representing HCS plates that contain segmentations that can be empty for some wells.

Here's a Fiji script I used to test the loading behavior:

#@ File tableFile

import org.embl.mobie.lib.io.StorageLocation
import org.embl.mobie.lib.table.saw.TableOpener
import org.embl.mobie.lib.table.TableDataFormat

tableStorageLocation = new StorageLocation()
tableStorageLocation.absolutePath = tableFile.getParent()
tableStorageLocation.defaultChunk = tableFile.getName()

table = TableOpener.open(tableStorageLocation, TableDataFormat.TSV)
print(table.columnNames())
@tischi
Copy link
Contributor

tischi commented Mar 28, 2023

To me it seems to make sense that this returns an empty column list?!

Does this then throw an error somewhere in MoBIE?

@imagejan
Copy link
Contributor Author

Yes, sorry I didn't specify. MoBIE throws this exception:

Exception in thread "Thread-173" java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsupportedOperationException: Could not match column names. []
	at org.embl.mobie.lib.ThreadHelper.waitUntilFinished(ThreadHelper.java:110)
	at org.embl.mobie.MoBIE.initDataSources(MoBIE.java:1130)
	at org.embl.mobie.lib.view.ViewManager.initData(ViewManager.java:347)
	at org.embl.mobie.lib.view.ViewManager.show(ViewManager.java:249)
	at org.embl.mobie.lib.ui.UserInterfaceHelper.lambda$createViewSelectionPanel$17(UserInterfaceHelper.java:778)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.util.concurrent.ExecutionException: java.lang.UnsupportedOperationException: Could not match column names. []
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at org.embl.mobie.lib.ThreadHelper.waitUntilFinished(ThreadHelper.java:102)
	... 5 more
Caused by: java.lang.UnsupportedOperationException: Could not match column names. []
	at org.embl.mobie.lib.table.TableDataFormat.getSegmentColumnNames(TableDataFormat.java:132)
	at org.embl.mobie.lib.table.saw.TableSawAnnotatedSegmentCreator.initColumns(TableSawAnnotatedSegmentCreator.java:43)
	at org.embl.mobie.lib.table.saw.TableSawAnnotatedSegmentCreator.<init>(TableSawAnnotatedSegmentCreator.java:33)
	at org.embl.mobie.MoBIE.createTableModel(MoBIE.java:1236)
	at org.embl.mobie.MoBIE.initDataSource(MoBIE.java:1158)
	at org.embl.mobie.MoBIE.lambda$initDataSources$2(MoBIE.java:1125)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	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)
	... 1 more

... and the BDV window stays empty.

I would have expected an empty table (but with all column headers present) to be shown (if this is the only table source), or that the merged table of all sources is displayed (obviously containing no row from this specific, empty, position).

But I can see this might be difficult due to how TableSaw's auto-guessing of column types depends on the presence of data.

(See also: jtablesaw/tablesaw#815)

@tischi
Copy link
Contributor

tischi commented Mar 28, 2023

Caused by: java.lang.UnsupportedOperationException: Could not match column names. []
	at org.embl.mobie.lib.table.TableDataFormat.getSegmentColumnNames(TableDataFormat.java:132)

This error is in fact from MoBIE trying to auto-guess whether this is an skimage or MorphoLibJ table (in the code that I showed you). What I do not understand is that for that it only needs the column names.

If you want to have a look you could compile the develop branch and put a breakpoint somewhere in getSegmentColumnNames in TableDataFormat.

@imagejan
Copy link
Contributor Author

What I do not understand is that for that it only needs the column names.

Exactly. MoBIE would only need the column names. But TableSaw (and therefore TableOpener) returns no columns although there are columns, they're just empty.

@tischi
Copy link
Contributor

tischi commented Mar 28, 2023

OK, sorry, then I did/do not understand your very first post: " the TableOpener.open() method returns a table with an empty column list:"

In the text below there are column names label_id anchor_z anchor_y anchor_x area bb_min_z bb_min_y bb_min_x bb_max_z bb_max_y bb_max_x... where did they come from?

@imagejan
Copy link
Contributor Author

Sorry for being not clear. I edited the original post to clarify these are examples of input .tsv files.

@tischi
Copy link
Contributor

tischi commented Mar 28, 2023

Ah, input, output... Sorry, I overlooked this.

Then, in fact, I guess the best would be to fix this in the issue that you linked.

Would you mind following up in the tableaw issue? The developer is usually very nice and responsive!

@imagejan
Copy link
Contributor Author

According to jtablesaw/tablesaw#815 (comment), the behavior of jtablesaw is intended (i.e. no columns are detected for empty tables).

My use case is a MoBIE dataset where we add a segmentation that only contains objects in some, but not all, wells. This leads to some of the default.tsv tables being header-only with no data. In this case, mobie-viewer-fiji throws an error and stops loading the dataset.
Maybe MoBIE viewer can be changed to read the header row in a different way, independent of tablesaw, to guess the table format?

@tischi
Copy link
Contributor

tischi commented Jun 22, 2023

Can you during creation detect that there are no objects and then just save no table at all for those wells? Actually, what about the corresponding label mask image, is that just an all zero image?

@imagejan
Copy link
Contributor Author

Yes, the underlying image is just an all-zero array (inside an ome-zarr structure in our case).

Not writing the table would also mean not adding the segmentation source for that well in the first place, right? So we'd have to create a sparse merged grid view, I guess that's possible...

@tischi
Copy link
Contributor

tischi commented Jun 27, 2023

@imagejan I just had a look and there is an List< int[] > positions argument in the merged grid transformation. It seems as if the code would work if some of those positions and the corresponding sources are missing.

Looks like a cleaner solution to me than having empty images and empty tables.

If you can make a minimal example project and there are issues with this approach, I can try to fix it!

@imagejan
Copy link
Contributor Author

@tischi Alright, I'm finally coming back to this issue.

It seems as if the code would work if some of those positions and the corresponding sources are missing.

The problem with this approach is that MoBIE Viewer seems to shift the minimum grid position to the origin of the BDV source, and this independently for each grid view. That is, if your images have e.g. well C03 at the top left corner (translating to grid position [ 2, 2 ]), but the sparse segmentation we're adding is empty for the entire first row and column, so its top left corner is well D04 (translating to grid position [ 3, 3 ]), then the grid view for the sparse segmentation is shifted one well up and left for the entire plate:

image

Also, if you happen to have a plate where all wells are empty for that segmentation channel, this empty grid view lets the validation (using mobie-utils-python) fail, and MoBIE Viewer doesn't list the channel in MoBIE's main panel.

/cc @SabineReither

@imagejan
Copy link
Contributor Author

For reference, the zero-mining of the grid seems to happen here:

private void setPositions( List< ? extends Image< T > > images, List< int[] > positions )
{
if ( positions == null )
{
this.positions = TransformHelper.createGridPositions( images.size() );
}
else
{
// zero-min the positions, because,
// I don't know why but, when the positions
// have a non-zero offset the rendered tiles
// do not match the RegionImage annotation
// locations

Maybe an accurate fix would be to avoid setting the min to zero, and (regarding the comment in the source code) instead fix the RegionImage to get displayed the correct way to have identical positions?

@tischi
Copy link
Contributor

tischi commented Jul 20, 2023

Maybe an accurate fix would be to avoid setting the min to zero, and (regarding the comment in the source code) instead fix the RegionImage to get displayed the correct way to have identical positions?

Yes, that would be good! I am travelling right now. Can only look into it in about 2 weeks...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants