Description
Currently, macOS users are unable to use interactive
mode with PyImageJ. See issue #23 for more details on getting the ImageJ GUI to work on macOS. Work on this issue resulted in the gui
mode for PyImageJ macOS users. While the GUI does work for macOS users, they unfortunately loose access to the REPL (unlike Linux and Windows users). This is because on macOS the Java AWT GUI must poll the interface for clicks and other input using Apple's AppKit framework (accessible via PyObcTools
from Python). This is done via an NSRunLoop
which blocks the console. In Python land this is done with AppHelper.runConsoleEventLoop()
from pyobjc
.
Here is a quick breakdown of my conclusions thus far in investigating this issue:
- I have tried a variety of Python threading strategies at all relevant levels of the PyImageJ stack:
pyimagej
,scijava
andjpype
. Trying to start and control the JVM andAppHelper
event loop from Python doesn't work. - I think (as well as some others in posts around the web) the
AppHelper.runConsoleEventLoop()
and the associated Objective-C code checks if its in the main thread or not. This means that any viable approaches here will likely involve sacrificing the main thread to Apple. - The
concurrent.futures
module is just a high level API for thethreading
module. It doesn't offer any solutions here. - Using the
installInterrupt=True
flag withAppHelper.runConsoleEventLoop()
doesn't help here. - Using Python's
asyncio
doesn't work (I also tried withinstallInterrupt=True
). - Using the
multiprocessing
module and starting a new process works...but of course the two processes can't share data easily. You can make a shared memory map and share data but this seems like it would require extensive rework. I didn't try this. Also,appose
could make this work nicely for us as well.
The latest approach I've seen and have tried (it didn't work but I could have been doing it wrong) is using AppKit's dispatch mechanism. The credit for this idea goes to this stackoverflow post where the user retsigam wanted to interact with the Bluetooth stack (which needed to run a console event loop) without locking up the REPL. This seemed like as close as I was going to get to finding an analogous problem in the wild. This solution relies on the libdispatch
which is also known as Grand Central Dispatch.
Digging a little further pyobjc
added bindings for libdispatch
in version 4.1. See this issue. Also in that issue is another user who is trying to resolve the console event loop block by starting the AppHelper.runCopnsoleEventLoop()
in a separate thread. Wayne solved this by using the dispatch mechanism documented here. This made think that using the dispatch mechanism was a viable approach here.