Skip to content

Commit 2ab6a33

Browse files
committed
Static do it demo - launches nepari with random data
1 parent 22b22c4 commit 2ab6a33

File tree

1 file changed

+111
-120
lines changed

1 file changed

+111
-120
lines changed

CP5/active_plugins/appose_demo.py

Lines changed: 111 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import zmq
1+
from time import sleep
2+
3+
import appose
4+
from appose.service import ResponseType
25
import numpy as np
36

4-
from cellprofiler_core.module.image_segmentation import ImageSegmentation
7+
from cellprofiler_core.module._module import Module
58
from cellprofiler_core.setting.do_something import DoSomething
6-
from cellprofiler_core.setting.text import Integer
7-
from cellprofiler_core.object import Objects
8-
9-
HELLO = "Hello"
10-
ACK = "Acknowledge"
11-
DENIED = "Denied"
9+
from cellprofiler_core.setting.subscriber import ImageSubscriber
10+
from cellprofiler_core.setting.text import Text
1211

1312
__doc__ = """\
1413
ApposeDemo
@@ -31,7 +30,55 @@
3130

3231
cite_paper_link = "https://doi.org/10.1016/1047-3203(90)90014-M"
3332

34-
class ApposeDemo(ImageSegmentation):
33+
qt_setup = """
34+
# CRITICAL: Qt must run on main thread on macOS.
35+
36+
from qtpy.QtWidgets import QApplication
37+
from qtpy.QtCore import Qt, QTimer
38+
import threading
39+
import sys
40+
41+
# Configure Qt for macOS before any QApplication creation
42+
QApplication.setAttribute(Qt.AA_MacPluginApplication, True)
43+
QApplication.setAttribute(Qt.AA_PluginApplication, True)
44+
QApplication.setAttribute(Qt.AA_DisableSessionManager, True)
45+
46+
# Create QApplication on main thread.
47+
qt_app = QApplication(sys.argv)
48+
49+
# Prevent Qt from quitting when last Qt window closes; we want napari to stay running.
50+
qt_app.setQuitOnLastWindowClosed(False)
51+
52+
task.export(qt_app=qt_app)
53+
task.update()
54+
55+
# Run Qt event loop on main thread.
56+
qt_app.exec()
57+
"""
58+
59+
qt_shutdown = """
60+
# Signal main thread to quit.
61+
qt_app.quit()
62+
"""
63+
64+
napari_show = """
65+
import napari
66+
import numpy as np
67+
68+
from superqt import ensure_main_thread
69+
70+
@ensure_main_thread
71+
def show(narr):
72+
napari.imshow(narr)
73+
74+
narr = np.random.random([512, 384])
75+
show(narr)
76+
task.outputs["shape"] = narr.shape
77+
"""
78+
79+
READY = False
80+
81+
class ApposeDemo(Module):
3582
category = "Image Processing"
3683

3784
module_name = "ApposeDemo"
@@ -40,137 +87,81 @@ class ApposeDemo(ImageSegmentation):
4087

4188
def create_settings(self):
4289
super().create_settings()
90+
91+
self.x_name = ImageSubscriber(
92+
"Select the input image", doc="Select the image you want to use."
93+
)
4394

44-
self.context = None
45-
self.server_socket = None
46-
47-
# TODO: launch server automatically, if necessary
48-
self.server_port = Integer(
49-
text="Server port number",
50-
value=7878,
51-
minval=0,
52-
doc="""\
53-
The port number which the server is listening on. The server must be launched manually first.
54-
""",
95+
self.package_path = Text(
96+
"Path to apposednapari environment",
97+
"/Users/Nodar/Developer/CellProfiler/apposednapari/.pixi/envs/default",
5598
)
56-
57-
# TODO: perform handshake automatically, if necessary
58-
self.server_handshake = DoSomething(
59-
"",
60-
"Perform Server Handshake",
61-
self.do_server_handshake,
99+
self.doit = DoSomething(
100+
"Do the thing",
101+
"Do it",
102+
self.do_it,
62103
doc=f"""\
63-
Press this button to do an initial handshake with the server.
64-
This must be done manually, once.
104+
Press this button to do the job.
65105
""",
66106
)
67107

68108
def settings(self):
69-
return super().settings() + [self.server_port, self.server_handshake]
109+
return super().settings() + [self.package_path, self.doit]
70110

71-
# ImageSegmentation defines this so we have to overide it
72111
def visible_settings(self):
73112
return self.settings()
74113

75-
# ImageSegmentation defines this so we have to overide it
76114
def volumetric(self):
77-
return False
115+
return True
78116

79117
def run(self, workspace):
80118
x_name = self.x_name.value
81119

82-
y_name = self.y_name.value
83-
84120
images = workspace.image_set
85121

86122
x = images.get_image(x_name)
87123

88-
dimensions = x.dimensions
89-
90124
x_data = x.pixel_data
91125

92-
y_data = self.do_server_execute(x_data)
93-
94-
y = Objects()
95-
96-
y.segmented = y_data
97-
98-
y.parent_image = x.parent_image
99-
100-
objects = workspace.object_set
101-
102-
objects.add_objects(y, y_name)
103-
104-
self.add_measurements(workspace)
105-
106126
if self.show_window:
107-
workspace.display_data.x_data = x_data
108-
109-
workspace.display_data.y_data = y_data
110-
111-
workspace.display_data.dimensions = dimensions
112-
113-
def do_server_handshake(self):
114-
port = str(self.server_port.value)
115-
domain = "localhost"
116-
socket_addr = f"tcp://{domain}:{port}"
117-
118-
if self.context:
119-
self.context.destroy()
120-
self.server_socket = None
121-
122-
self.context = zmq.Context()
123-
self.server_socket = self.context.socket(zmq.PAIR)
124-
self.server_socket.copy_threshold = 0
125-
c = self.server_socket.connect(socket_addr)
126-
127-
print("Setup socket at", socket_addr, "connected to", c)
128-
129-
self.server_socket.send_string(HELLO)
130-
response = self.server_socket.recv_string()
131-
132-
if response == ACK:
133-
print("Received correct response", response)
134-
else:
135-
print("Received unexpected response", response)
136-
137-
def do_server_execute(self, im_data):
138-
dummy_data = lambda: np.array([[]])
139-
140-
socket = self.server_socket
141-
header = np.lib.format.header_data_from_array_1_0(im_data)
142-
143-
socket.send_json(header)
144-
145-
ack = socket.recv_string()
146-
if ack == ACK:
147-
print("header acknowledged:", ack)
148-
else:
149-
print("unexpected response", ack)
150-
return dummy_data()
151-
152-
socket.send(im_data, copy=False)
153-
154-
ack = socket.recv_string()
155-
if ack == ACK:
156-
print("image data acknowledged", ack)
157-
elif ack == DENIED:
158-
print("image data denied, aborting", ack)
159-
return dummy_data()
160-
else:
161-
print("unknown response to image data", ack)
162-
return dummy_data()
163-
164-
return_header = socket.recv_json()
165-
print("received return header", return_header)
166-
167-
print("acknowledging header reciept")
168-
socket.send_string(ACK)
169-
170-
print("waiting for image data")
171-
172-
label_data_buf = socket.recv(copy=False)
173-
labels = np.frombuffer(label_data_buf, dtype=return_header['descr'])
174-
labels.shape = return_header['shape']
175-
print("returning label data", labels.shape)
176-
return labels
127+
...
128+
129+
def do_it(self):
130+
env = appose.base(str(self.package_path)).build()
131+
with env.python() as python:
132+
# Print Appose events verbosely, for debugging purposes.
133+
python.debug(print)
134+
135+
# Start the Qt application event loop in the worker process.
136+
print("Starting Qt app event loop")
137+
setup = python.task(qt_setup, queue="main")
138+
def check_ready(event):
139+
if event.response_type == ResponseType.UPDATE:
140+
print("Got update event! Marking Qt as ready")
141+
global READY
142+
READY = True
143+
print("Ready...", READY)
144+
print("attempting to start to listen", flush=True)
145+
setup.listen(check_ready)
146+
print("attempting start setup", flush=True)
147+
setup.start()
148+
print("Waiting for Qt startup...", flush=True)
149+
global READY
150+
while not READY:
151+
print("sleeping", READY)
152+
sleep(0.1)
153+
print("Qt is ready!", flush=True)
154+
155+
# Create a test image in shared memory.
156+
ndarr = appose.NDArray(dtype="float64", shape=[512, 384])
157+
# Fill the array with random values.
158+
# There's probably a slicker way without needing to slice/copy...
159+
ndarr.ndarray()[:] = np.random.random(ndarr.shape)
160+
161+
# Actually do a real thing with napari: create and show an image.
162+
print("Showing image with napari...", flush=True)
163+
task = python.task(napari_show, inputs={"ndarr": ndarr})
164+
165+
task.wait_for()
166+
shape = task.outputs["shape"]
167+
print(f"Task complete! Got shape: {shape}", flush=True)

0 commit comments

Comments
 (0)