Skip to content

Commit 9f0f2c2

Browse files
committed
Setup apposednepari
- create appose/napari env - launch nepari and transfer image on run
1 parent 1766d60 commit 9f0f2c2

File tree

6 files changed

+3562
-0
lines changed

6 files changed

+3562
-0
lines changed

CP5/active_plugins/appose_demo.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import os
2+
from time import sleep
3+
4+
import appose
5+
from appose.service import ResponseType
6+
import numpy as np
7+
8+
from cellprofiler_core.module._module import Module
9+
from cellprofiler_core.setting.do_something import DoSomething
10+
from cellprofiler_core.setting.subscriber import ImageSubscriber
11+
from cellprofiler_core.setting.text import Text
12+
13+
__doc__ = """\
14+
ApposeDemo
15+
============
16+
17+
**ApposeDemo** is a demo of runnin napari through Appose.
18+
19+
20+
TODO: more docs
21+
22+
|
23+
24+
============ ============ ===============
25+
Supports 2D? Supports 3D? Respects masks?
26+
============ ============ ===============
27+
YES YES YES
28+
============ ============ ===============
29+
30+
"""
31+
32+
cite_paper_link = "https://doi.org/10.1016/1047-3203(90)90014-M"
33+
34+
qt_setup = """
35+
# CRITICAL: Qt must run on main thread on macOS.
36+
37+
from qtpy.QtWidgets import QApplication
38+
from qtpy.QtCore import Qt, QTimer
39+
import threading
40+
import sys
41+
42+
# Configure Qt for macOS before any QApplication creation
43+
QApplication.setAttribute(Qt.AA_MacPluginApplication, True)
44+
QApplication.setAttribute(Qt.AA_PluginApplication, True)
45+
QApplication.setAttribute(Qt.AA_DisableSessionManager, True)
46+
47+
# Create QApplication on main thread.
48+
qt_app = QApplication(sys.argv)
49+
50+
# Prevent Qt from quitting when last Qt window closes; we want napari to stay running.
51+
qt_app.setQuitOnLastWindowClosed(False)
52+
53+
task.export(qt_app=qt_app)
54+
task.update()
55+
56+
# Run Qt event loop on main thread.
57+
qt_app.exec()
58+
"""
59+
60+
qt_shutdown = """
61+
# Signal main thread to quit.
62+
qt_app.quit()
63+
"""
64+
65+
napari_show = """
66+
import napari
67+
import numpy as np
68+
69+
from superqt import ensure_main_thread
70+
71+
@ensure_main_thread
72+
def show(narr):
73+
napari.imshow(narr)
74+
75+
show(ndarr.ndarray())
76+
task.outputs["shape"] = ndarr.shape
77+
"""
78+
79+
READY = False
80+
81+
class ApposeDemo(Module):
82+
category = "Image Processing"
83+
84+
module_name = "ApposeDemo"
85+
86+
variable_revision_number = 1
87+
88+
def create_settings(self):
89+
super().create_settings()
90+
91+
self.x_name = ImageSubscriber(
92+
"Select the input image", doc="Select the image you want to use."
93+
)
94+
95+
self.package_path = Text(
96+
"Path to apposednapari environment",
97+
f"{os.path.dirname(__file__)}/apposednapari/.pixi/envs/default",
98+
)
99+
self.doit = DoSomething(
100+
"Do the thing",
101+
"Do it",
102+
lambda: self.send_to_napari(),
103+
doc=f"""\
104+
Press this button to do the job.
105+
""",
106+
)
107+
108+
def settings(self):
109+
return super().settings() + [self.x_name, self.package_path, self.doit]
110+
111+
def visible_settings(self):
112+
return self.settings()
113+
114+
def volumetric(self):
115+
return True
116+
117+
def run(self, workspace):
118+
x_name = self.x_name.value
119+
120+
images = workspace.image_set
121+
122+
x = images.get_image(x_name)
123+
124+
x_data = x.pixel_data
125+
126+
self.send_to_napari(x_data)
127+
128+
if self.show_window:
129+
...
130+
131+
# credit to Curtis Rueden:
132+
# https://github.com/ctrueden/appose-napari-demo/blob/a803e50347a023578afdd9ddc2c287567d5445fc/napari-show.py
133+
def send_to_napari(self, img_data=None):
134+
env = appose.base(str(self.package_path)).build()
135+
with env.python() as python:
136+
# Print Appose events verbosely, for debugging purposes.
137+
python.debug(print)
138+
139+
# Start the Qt application event loop in the worker process.
140+
print("Starting Qt app event loop")
141+
setup = python.task(qt_setup, queue="main")
142+
def check_ready(event):
143+
if event.response_type == ResponseType.UPDATE:
144+
print("Got update event! Marking Qt as ready")
145+
global READY
146+
READY = True
147+
print("Ready...", READY)
148+
print("attempting to start to listen")
149+
setup.listen(check_ready)
150+
print("attempting start setup")
151+
setup.start()
152+
print("Waiting for Qt startup...")
153+
global READY
154+
while not READY:
155+
#print("sleeping", READY)
156+
sleep(0.1)
157+
print("Qt is ready!")
158+
159+
if img_data is None:
160+
img_data = np.random.random([512, 384]).astype("float64")
161+
162+
# Create a test image in shared memory.
163+
ndarr = appose.NDArray(dtype=str(img_data.dtype), shape=img_data.shape)
164+
# There's probably a slicker way without needing to slice/copy...
165+
ndarr.ndarray()[:] = img_data[:]
166+
167+
# Actually do a real thing with napari: create and show an image.
168+
print("Showing image with napari...")
169+
task = python.task(napari_show, inputs={"ndarr": ndarr})
170+
171+
task.wait_for()
172+
shape = task.outputs["shape"]
173+
print(f"Task complete! Got shape: {shape}")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SCM syntax highlighting & preventing 3-way merges
2+
pixi.lock merge=binary linguist-language=YAML linguist-generated=true
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# pixi environments
2+
.pixi

0 commit comments

Comments
 (0)