BIOP / abba_python

ABBA, controlled from python
BSD 3-Clause "New" or "Revised" License
7 stars 3 forks source link

Export to Qupath does not work in headless mode from a script #9

Open GuillaumeLeGoc opened 1 year ago

GuillaumeLeGoc commented 1 year ago

Description

I want to export registration to the Qupath project programmatically from python. To do this, I launch an ABBA instance in headless mode, I load a state file and run the export registration command :

aligner = abba.Abba('Adult Mouse Brain - Allen Brain Atlas V3p1', headless=True)
aligner.state_load(os.path.join(inputdir, inputfile))
aligner.select_all_slices()
aligner.export_registration_to_qupath(erase_previous_file=True)

The following line is printed : Exporting slice img_001.ome.tiff registration to QuPath But there is nothing written in the Qupath project.

There is an error raised during the loading of the state file (see below), but this error did not prevent me from doing things with abba_python previously (eg. converting coordinates).

Doing the same thing not in headless mode (eg. aligner = abba.Abba('Adult Mouse Brain - Allen Brain Atlas V3p1')), the output is more talkative, those lines are also printed :

Multipositioner : Save slice ROI to quPath project path\to\qproj\data\1\ABBA-RoiSet-Adult Mouse Brain - Allen Brain Atlas V3p1.zip
Multipositioner : Save transformation to quPath project path\to\qproj\data\1\ABBA-Transform-Adult Mouse Brain - Allen Brain Atlas V3p1.json

And the files are indeed exported.

So it seems the multipositionner does not really exist in headless mode and Qupath export relies on it (but converting coordinates does not so that is why the latter works) ?

Output

[java.lang.Enum.toString] [ERROR] Multipositioner : null
[ERROR] Command errored: ABBA - Load State
java.awt.HeadlessException
        at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
        at java.awt.Window.<init>(Window.java:536)
        at java.awt.Frame.<init>(Frame.java:420)
        at java.awt.Frame.<init>(Frame.java:385)
        at javax.swing.JFrame.<init>(JFrame.java:189)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.lambda$new$2(MultiSlicePositioner.java:134)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.lambda$new$4(MultiSlicePositioner.java:144)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.loadState(MultiSlicePositioner.java:1181)
        at ch.epfl.biop.atlas.aligner.command.ABBAStateLoadCommand.run(ABBAStateLoadCommand.java:27)
        at org.scijava.command.CommandModule.run(CommandModule.java:196)
        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:748)
[java.lang.Enum.toString] [ERROR] Command errored: ABBA - Load State
java.awt.HeadlessException
        at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
        at java.awt.Window.<init>(Window.java:536)
        at java.awt.Frame.<init>(Frame.java:420)
        at java.awt.Frame.<init>(Frame.java:385)
        at javax.swing.JFrame.<init>(JFrame.java:189)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.lambda$new$2(MultiSlicePositioner.java:134)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.lambda$new$4(MultiSlicePositioner.java:144)
        at ch.epfl.biop.atlas.aligner.MultiSlicePositioner.loadState(MultiSlicePositioner.java:1181)
        at ch.epfl.biop.atlas.aligner.command.ABBAStateLoadCommand.run(ABBAStateLoadCommand.java:27)
        at org.scijava.command.CommandModule.run(CommandModule.java:196)
        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:748)
GuillaumeLeGoc commented 1 year ago

Hey, I meant : there is no new files written by ABBA in the Qupath project, eg. no "Adult Mouse Brain - Allen Brain Atlas V3p1-Ontology.json" in the root of the project, and no "ABBA...zip" nor "ABBA...json" files in the image entries. But those files are created as expected without headless mode (but not touching the GUI except the angles changed prompt).

Guillaume Le Goc

------- Original Message ------- On Tuesday, July 25th, 2023 at 15:16, Nicolas Chiaruttini @.***> wrote:

But there is nothing written in the Qupath project.

Just to be sure: you do not see any file in the project entry ?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

NicoKiaru commented 1 year ago

Yeah, I read more carefully your message and got it, that's why I deleted my message sorry !

I'll need a bit of time, debugging java in python is not sure super straightforward

NicoKiaru commented 1 year ago

I've looked quickly and there's no clear dependency on the user interface. I'll have to dig more carefully

NicoKiaru commented 1 year ago

Regarding the first error: I had a bunch of graphical classes for logging even in the one that's suppose to work headless. So I'll remove them.

NicoKiaru commented 1 year ago

Hum, I can't reproduce the issue. Have you waited enough time ? Maybe the issue is that it's not a blocking command ?

Can you try on one slice:

mp = aligner.mp
aligner.deselect_all_slices()
mp.selectSlice(mp.getSlices().get(0))
aligner.export_registration_to_qupath(erase_previous_file=True)

Wait ~30s and check if the expected file is written on the entry folder of this slice ?

Maybe double check that you are targetting the right QuPath project ?

If it's something else that can be tricky. Maybe there's an error which is swallowed because of the error log.

If none of this works, I can make a special jar that logs a bunch of other things to see where the problem

GuillaumeLeGoc commented 1 year ago

Hi, sorry for the late reply, I was away for a while. Thanks for looking into it. So I tried again directly from an IPython console, and it works fine, with the original commands provided in OP :

aligner.select_all_slices()
aligner.export_registration_to_qupath(erase_previous_file=True)

It also works from a regular python console, but does not work (eg. files are not exported) executing the python script (python script.py). I have no idea what's happening. I can just notice that the regular python prompt is not released after aligner.export_registration_to_qupath(erase_previous_file=True). Unfortunately I have no knowledge about the differences of execution between python prompt and from a script...

NicoKiaru commented 1 year ago

Ah! Then I think the issue comes from the asynchronous nature of the method.

Please try to add at the end of your script:

# all tasks/registrations/exports are enqueued and executed asynchronously
# if you want to wait that all tasks are finished, then add:
aligner.wait_for_end_of_tasks()

If that's the issue, then I need to change the method or improve the documentation. Probably I'll add an extra parameter async that would be false by default.

GuillaumeLeGoc commented 1 year ago

Yay, you nailed it ! It works fine from a script now. I think it's fine to just add this to the documentation. I saw it in the notebooks but is it useful from a notebook (since it works fine from an IPython console, I assume it works from a notebook) ? It would be good to remind this in the readme, something like "tasks are enqueued so a script needs to wait for the command to finish before terminating it, with aligner.wait_for_end_of_tasks()".

Cheers !

NicoKiaru commented 1 year ago

Reopening the issue to keep track of the documentation modification

GuillaumeLeGoc commented 1 year ago

Hey, Sorry, I feel like I'm always bringing bad news... I just noticed that exporting to Qupath from abba_python does not yield the same results as with the export from Fiji. It's as if some transformations are not applied during the python export.

I checked, indeed it seems transformations done through "Interactive Transform" are not applied before exporting (I rotated a slice by a large angle, it shows when exporting from the UI but not from python). (*)

Exporting from the UI launched from Python yields the same result as in regular Fiji UI.

I can open a new issue to help you track this if you prefer.

Cheers !

And again, sorry... Do you have echoes from other users of abba_python ? It feels like I'm either the only one to use it, the only one to complain or the only for who it does not work as expected :'(

[EDIT] () Launching ABBA from Python not* in headless mode, without even showing the bdv UI, and exporting from the python prompt (aligner.export_registration_to_qupath(erase_previous_file=True)) seems to yield the good result.

NicoKiaru commented 1 year ago

Hi @GuillaumeLeGoc and thanks for the report!

It feels like I'm either the only one to use it, the only one to complain or the only for who it does not work as expected :'(

:-) Yes, there's much less users in python than within the regular Fiji. You may be the only one or not far from it.

I'm not sure about this new issue. I'll try to contact you by email

GuillaumeLeGoc commented 1 year ago

The python script used to export to QuPath.

# --- Imports
import os
from abba_python import abba

# --- Parameters
abba_file = os.path.abspath("path/to/state.abba")

# --- Processing

# Get ABBA
aligner = abba.Abba('Adult Mouse Brain - Allen Brain Atlas V3p1', headless=True)    # does not work in headless mode

# Load state
try:
    aligner.state_load(abba_file)
except:
    pass

# get multipos
mp = aligner.mp
aligner.deselect_all_slices()
mp.selectSlice(mp.getSlices().get(0))

# Export to Qupath project
aligner.export_registration_to_qupath(erase_previous_file=True)
aligner.wait_for_end_of_tasks()