Last night I tried coding a script to pop up a napari viewer and the ImageJ user interface side-by-side.
""" napari + Fiji """ import objc from Foundation import * from AppKit import * from PyObjCTools import AppHelper import imagej, napari def wrap(f): class AppDelegate (NSObject): def init(self): self = objc.super(AppDelegate, self).init() if self is None: return None return self def runjava_(self, arg): f() def applicationDidFinishLaunching_(self, aNotification): self.performSelectorInBackground_withObject_("runjava:", 0) app = NSApplication.sharedApplication() delegate = AppDelegate.alloc().init() NSApp().setDelegate_(delegate) # this is necessary to have keyboard events sent to the UI; # basically this call makes the script act like an OS X application, # with Dock icon and everything NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular) AppHelper.runEventLoop() def start_imagej(): ij = imagej.init('sc.fiji:fiji:LATEST+net.imagej:imagej-legacy:0.37.0+org.scijava:script-editor:0.5.3', headless=False) ij.ui().showUI("swing") print('--> ImageJ started') print('--> Starting napari') with napari.gui_qt(): viewer = napari.Viewer() print('--> Starting imagej') wrap(start_imagej) print('--> Aaaand done')
Since I have a MacBook, I need to deal with the threading concerns of macOS. Specifically: in order to display the Fiji user interface, Java AWT must be started on the Cocoa event loop. This can be achieved using PyObjC; details here.
However, attempting to invoke the napari viewer from the Cocoa event loop thread crashes the program, with errors like:
2019-12-11 16:07:07.636 python[50393:3589960] pid(50393)/euid(503) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!! 2019-12-11 16:07:08.166 python[50393:3589960] Apple AWT Internal Exception: <class 'RuntimeError'>: There is no current event loop in thread 'Dummy-1'. 2019-12-11 16:07:08.166 python[50393:3589960] *** Terminating app due to uncaught exception 'OC_PythonException', reason: '<class 'RuntimeError'>: There is no current event loop in thread 'Dummy-1'.' *** First throw call stack: ( 0 CoreFoundation 0x00007fff36c2dacd __exceptionPreprocess + 256 1 libobjc.A.dylib 0x00007fff6130aa17 objc_exception_throw + 48 2 CoreFoundation 0x00007fff36c47629 -[NSException raise] + 9 3 _objc.cpython-37m-darwin.so 0x00000001068f5a4e PyObjCErr_ToObjCWithGILState + 46 4 _objc.cpython-37m-darwin.so 0x00000001068cae63 method_stub + 5283 5 _objc.cpython-37m-darwin.so 0x00000001068f53e0 ffi_closure_unix64_inner + 720 6 _objc.cpython-37m-darwin.so 0x00000001068f48f6 ffi_closure_unix64 + 70 7 libsystem_pthread.dylib 0x00007fff62ccd2eb _pthread_body + 126 8 libsystem_pthread.dylib 0x00007fff62cd0249 _pthread_start + 66 9 libsystem_pthread.dylib 0x00007fff62ccc40d thread_start + 13 ) libc++abi.dylib: terminating with uncaught exception of type NSException
I can start napari from the main thread (just dedent the “Starting napari” lines one level ), but then: A) the script blocks until I close the napari viewer window; and B) even after closing the window, the ImageJ UI never appears even though the code to start ImageJ does then execute.
Are there any people sufficiently expert in macOS+Python who can advise on the best way forward regarding these threading issues? Have any of you combined the QT and Cocoa/Objective-C frameworks? Is this feasible?
@skalarproduktraum suggested to me that it might not be feasible within the same process, in which case the next step would be to research ways of launching java and python in two separate processes that still share memory somehow.
Ideas for troubleshooting are very welcome.
In the meantime, since I’m rather stuck, I am going to start coding support on the Python side for executing SciJava commands. This could eventually lead to all/most ImageJ2 plugins becoming magically available in the napari UI.