Skip to content

Commit 2c315d8

Browse files
committed
Let macOS interactive mode work in a notebook
* Check for IPython and call enable_gui("osx") as appropriate. * Still fall back to the pthread library check if no IPython. * Give a much better error message when we think it won't work. * Let the user force interactive mode using the :force suffix. But admonish them thoroughly for doing it, to avoid fallout. I really don't understand how the IPython magic works, given that pthread still reports the current thread as 1/main after enabling the magic. Maybe it starts the event loop on a non-main thread?! But regardless, this logic is hopefully a step forward.
1 parent 9c75ef5 commit 2c315d8

File tree

1 file changed

+66
-15
lines changed

1 file changed

+66
-15
lines changed

src/imagej/__init__.py

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from enum import Enum
4343
from functools import lru_cache
4444
from pathlib import Path
45+
from textwrap import dedent
4546
from typing import Tuple, Union
4647

4748
import numpy as np
@@ -1205,6 +1206,10 @@ def init(
12051206
12061207
ij = imagej.init("sc.fiji:fiji", mode=imagej.Mode.GUI)
12071208
"""
1209+
force = mode.endswith(":force")
1210+
if force:
1211+
mode = mode[:-6]
1212+
12081213
if headless is not None:
12091214
_logger.warning(
12101215
"The headless flag of imagej.init is deprecated. "
@@ -1215,10 +1220,31 @@ def init(
12151220
macos = sys.platform == "darwin"
12161221

12171222
if macos and mode == Mode.INTERACTIVE:
1218-
# check for main thread only on macOS
1219-
if _macos_is_main_thread():
1223+
if not _macos_enable_interactive(force=force):
12201224
raise EnvironmentError(
1221-
"Sorry, the interactive mode is not available on macOS."
1225+
dedent("""\
1226+
Cannot enable interactive mode in this environment.
1227+
On macOS, the CoreFoundation/AppKit event loop must
1228+
be running on the process's main thread.
1229+
1230+
If you are in an IPython/Jupyter environment,
1231+
you can try using the `%gui osx` magic.
1232+
1233+
If you are running with plain python, your program
1234+
appears to be running on the process's main thread,
1235+
meaning your program will hang when attempting to
1236+
perform any GUI-related action like showing the UI.
1237+
You could try launching via Jaunch...
1238+
1239+
Or, if you really want to try it anyway, you can
1240+
pass `mode="interactive:force"` for the init mode,
1241+
which will bypass this check. But this mode is
1242+
UNSUPPORTED and the PyImageJ team CANNOT SUPPORT YOU
1243+
if you do this. Please do not publish scripts that
1244+
use `interactive:force`; rather, let's improve
1245+
PyImageJ's `_macos_enable_interactive()` function
1246+
to more smartly detect your deployment situation.
1247+
""")
12221248
)
12231249

12241250
if not sj.jvm_started():
@@ -1551,25 +1577,50 @@ def _includes_imagej_legacy(items: list):
15511577
return any(item.startswith("net.imagej:imagej-legacy") for item in items)
15521578

15531579

1554-
def _macos_is_main_thread():
1555-
"""Detect if the current thread is the main thread on macOS.
1580+
def _macos_enable_interactive(force: bool = False) -> bool:
1581+
"""
1582+
Make interactive mode work on macOS if possible.
15561583
1557-
:return: Boolean indicating if the current thread is the main thread.
1584+
:return: True if interactive mode will work, False if not.
15581585
"""
1559-
# try to load the pthread library
1586+
_logger.debug("Attempting to enable interactive mode.")
1587+
1588+
# Check for an IPython/Jupyter environment.
15601589
try:
1590+
import IPython
1591+
1592+
ipy = IPython.get_ipython()
1593+
if ipy is None:
1594+
_logger.debug("No IPython environment found.")
1595+
else:
1596+
# Engage the `%gui osx` magic!
1597+
ipy.enable_gui("osx")
1598+
_logger.debug("Enabled IPython osx gui.")
1599+
return True
1600+
except Exception as exc:
1601+
_logger.debug("Failed to converse with IPython.")
1602+
_logger.debug(exc)
1603+
1604+
# Try to ask the pthread library.
1605+
try:
1606+
# Ask pthread whether we're on the main thread.
15611607
pthread = cdll.LoadLibrary("libpthread.dylib")
1608+
thread_num = pthread.pthread_main_np()
1609+
_logger.debug(f"pthread reports thread number {thread_num}.")
1610+
if thread_num != 1:
1611+
# Not on the main thread! Worth a try...
1612+
return True
15621613
except OSError as exc:
1563-
_log_exception(_logger, exc)
1564-
print("No pthread library found.")
1565-
# assume the current thread is the main thread
1566-
return True
1614+
_logger.debug("Failed to converse with pthread library.")
1615+
_logger.debug(exc)
15671616

1568-
# detect if the current thread is the main thread
1569-
if pthread.pthread_main_np() == 1:
1617+
# Seems like we're on the main thread outside IPython.
1618+
if force:
1619+
_logger.warning("Interactive mode forced. Your program might hang.")
15701620
return True
1571-
else:
1572-
return False
1621+
1622+
_logger.debug("All checks failed. Interactive mode not available.")
1623+
return False
15731624

15741625

15751626
def _set_ij_env(ij_dir):

0 commit comments

Comments
 (0)