imagej / imagej/pyimagej

Make interactive mode work on macOS

Open
#298 6 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
534
Forks
95
PR merge metrics
No merged PRs in 30d

Description

Currently, macOS users are unable to use `interactive` mode with PyImageJ. See issue https://github.com/imagej/pyimagej/issues/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`](https://developer.apple.com/documentation/foundation/nsrunloop) which blocks the console. In Python land this is done with [`AppHelper.runConsoleEventLoop()`](https://github.com/ronaldoussoren/pyobjc/blob/dee913c26b276112d061c3ca953275ec106344b1/pyobjc-framework-Cocoa/Lib/PyObjCTools/AppHelper.py#L243-L269) 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` and `jpype`. Trying to start and control the JVM and `AppHelper` 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`](https://docs.python.org/3/library/concurrent.futures.html#module-concurrent.futures) module is just a high level API for the `threading` module. It doesn't offer any solutions here.
- Using the `installInterrupt=True` flag with `AppHelper.runConsoleEventLoop()` doesn't help here.
- Using Python's [`asyncio`](https://docs.python.org/3/library/asyncio.html) doesn't work (I also tried with `installInterrupt=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](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) 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](https://stackoverflow.com/questions/48958267/how-can-i-use-corebluetooth-for-python-without-giving-up-the-main-thread) where the user [retsigam](https://stackoverflow.com/users/2909225/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](https://en.wikipedia.org/wiki/Grand_Central_Dispatch).

Digging a little further `pyobjc` added bindings for `libdispatch` in version 4.1. See this [issue](https://github.com/ronaldoussoren/pyobjc/issues/215). 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](https://gist.github.com/WayneKeenan/bcf018413547c3abe7b6de3716b87bfa). This made think that using the dispatch mechanism was a viable approach here.

Contributor guide

No contributing guide indexed for this repository

Research direction

Begin with the linked macOS GUI issue and the AppHelper.runConsoleEventLoop() entry point; review the attempted threading, asyncio, multiprocessing, and libdispatch approaches described here. Done means macOS users can use the ImageJ GUI and retain a working Python REPL without requiring the extensive shared-state rework noted in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.