Initial working zmq pub sub and topic integration
This commit is contained in:
@ -6,6 +6,5 @@ display is a function that displays these in their own windows.
|
|||||||
|
|
||||||
__version__ = "1.1.1"
|
__version__ = "1.1.1"
|
||||||
|
|
||||||
from .window.subscriber_windows import display, breakpoint_display, read_updates
|
from .window.subscriber_windows import display, breakpoint_display, read_updates, publish_updates
|
||||||
from .frame.frame_publishing import publish_updates_zero_mq, publish_updates_ros
|
|
||||||
from . import effects
|
from . import effects
|
||||||
|
@ -27,6 +27,7 @@ import numpy as np
|
|||||||
|
|
||||||
from displayarray.frame import subscriber_dictionary
|
from displayarray.frame import subscriber_dictionary
|
||||||
from .np_to_opencv import NpCam
|
from .np_to_opencv import NpCam
|
||||||
|
from .zmq_to_opencv import ZmqCam
|
||||||
from displayarray._uid import uid_for_source
|
from displayarray._uid import uid_for_source
|
||||||
|
|
||||||
from typing import Union, Tuple, Optional, Dict, Any, List, Callable
|
from typing import Union, Tuple, Optional, Dict, Any, List, Callable
|
||||||
@ -127,7 +128,10 @@ def pub_cam_loop_opencv(
|
|||||||
name = uid_for_source(cam_id)
|
name = uid_for_source(cam_id)
|
||||||
|
|
||||||
if isinstance(cam_id, (int, str)):
|
if isinstance(cam_id, (int, str)):
|
||||||
cam: Union[NpCam, cv2.VideoCapture] = cv2.VideoCapture(cam_id)
|
if isinstance(cam_id, str) and cam_id.startswith('tcp'):
|
||||||
|
cam = ZmqCam(cam_id)
|
||||||
|
else:
|
||||||
|
cam: Union[NpCam,ZmqCam, cv2.VideoCapture] = cv2.VideoCapture(cam_id)
|
||||||
elif isinstance(cam_id, np.ndarray):
|
elif isinstance(cam_id, np.ndarray):
|
||||||
cam = NpCam(cam_id)
|
cam = NpCam(cam_id)
|
||||||
else:
|
else:
|
||||||
@ -216,124 +220,3 @@ def pub_cam_thread(
|
|||||||
uid_dict[name] = t
|
uid_dict[name] = t
|
||||||
t.start()
|
t.start()
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
async def publish_updates_zero_mq(
|
|
||||||
*vids,
|
|
||||||
callbacks: Optional[
|
|
||||||
Union[Dict[Any, FrameCallable], List[FrameCallable], FrameCallable]
|
|
||||||
] = None,
|
|
||||||
fps_limit=float("inf"),
|
|
||||||
size=(-1, -1),
|
|
||||||
end_callback: Callable[[], bool] = lambda: False,
|
|
||||||
blocking=False,
|
|
||||||
publishing_address="tcp://127.0.0.1:5600",
|
|
||||||
prepend_topic="",
|
|
||||||
flags=0,
|
|
||||||
copy=True,
|
|
||||||
track=False,
|
|
||||||
):
|
|
||||||
"""Publish frames to ZeroMQ when they're updated."""
|
|
||||||
import zmq
|
|
||||||
from displayarray import read_updates
|
|
||||||
|
|
||||||
ctx = zmq.Context()
|
|
||||||
s = ctx.socket(zmq.PUB)
|
|
||||||
s.bind(publishing_address)
|
|
||||||
|
|
||||||
if not blocking:
|
|
||||||
flags |= zmq.NOBLOCK
|
|
||||||
|
|
||||||
try:
|
|
||||||
for v in read_updates(vids, callbacks, fps_limit, size, end_callback, blocking):
|
|
||||||
if v:
|
|
||||||
for vid_name, frame in v.items():
|
|
||||||
md = dict(
|
|
||||||
dtype=str(frame.dtype),
|
|
||||||
shape=frame.shape,
|
|
||||||
name=prepend_topic + vid_name,
|
|
||||||
)
|
|
||||||
s.send_json(md, flags | zmq.SNDMORE)
|
|
||||||
s.send(frame, flags, copy=copy, track=track)
|
|
||||||
if fps_limit:
|
|
||||||
await asyncio.sleep(1.0 / fps_limit)
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
vid_names = [uid_for_source(name) for name in vids]
|
|
||||||
for v in vid_names:
|
|
||||||
subscriber_dictionary.stop_cam(v)
|
|
||||||
|
|
||||||
|
|
||||||
async def publish_updates_ros(
|
|
||||||
*vids,
|
|
||||||
callbacks: Optional[
|
|
||||||
Union[Dict[Any, FrameCallable], List[FrameCallable], FrameCallable]
|
|
||||||
] = None,
|
|
||||||
fps_limit=float("inf"),
|
|
||||||
size=(-1, -1),
|
|
||||||
end_callback: Callable[[], bool] = lambda: False,
|
|
||||||
blocking=False,
|
|
||||||
node_name="displayarray",
|
|
||||||
publisher_name="npy",
|
|
||||||
rate_hz=None,
|
|
||||||
dtype=None,
|
|
||||||
):
|
|
||||||
"""Publish frames to ROS when they're updated."""
|
|
||||||
import rospy
|
|
||||||
from rospy.numpy_msg import numpy_msg
|
|
||||||
import std_msgs.msg
|
|
||||||
from displayarray import read_updates
|
|
||||||
|
|
||||||
def get_msg_type(dtype):
|
|
||||||
if dtype is None:
|
|
||||||
msg_type = {
|
|
||||||
np.float32: std_msgs.msg.Float32(),
|
|
||||||
np.float64: std_msgs.msg.Float64(),
|
|
||||||
np.bool: std_msgs.msg.Bool(),
|
|
||||||
np.char: std_msgs.msg.Char(),
|
|
||||||
np.int16: std_msgs.msg.Int16(),
|
|
||||||
np.int32: std_msgs.msg.Int32(),
|
|
||||||
np.int64: std_msgs.msg.Int64(),
|
|
||||||
np.str: std_msgs.msg.String(),
|
|
||||||
np.uint16: std_msgs.msg.UInt16(),
|
|
||||||
np.uint32: std_msgs.msg.UInt32(),
|
|
||||||
np.uint64: std_msgs.msg.UInt64(),
|
|
||||||
np.uint8: std_msgs.msg.UInt8(),
|
|
||||||
}[dtype]
|
|
||||||
else:
|
|
||||||
msg_type = (
|
|
||||||
dtype # allow users to use their own custom messages in numpy arrays
|
|
||||||
)
|
|
||||||
return msg_type
|
|
||||||
|
|
||||||
publishers: Dict[str, rospy.Publisher] = {}
|
|
||||||
rospy.init_node(node_name, anonymous=True)
|
|
||||||
try:
|
|
||||||
for v in read_updates(vids, callbacks, fps_limit, size, end_callback, blocking):
|
|
||||||
if v:
|
|
||||||
if rospy.is_shutdown():
|
|
||||||
break
|
|
||||||
for vid_name, frame in v.items():
|
|
||||||
if vid_name not in publishers:
|
|
||||||
dty = frame.dtype if dtype is None else dtype
|
|
||||||
publishers[vid_name] = rospy.Publisher(
|
|
||||||
publisher_name + vid_name,
|
|
||||||
numpy_msg(get_msg_type(dty)),
|
|
||||||
queue_size=10,
|
|
||||||
)
|
|
||||||
publishers[vid_name].publish(frame)
|
|
||||||
if rate_hz:
|
|
||||||
await asyncio.sleep(1.0 / rate_hz)
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
vid_names = [uid_for_source(name) for name in vids]
|
|
||||||
for v in vid_names:
|
|
||||||
subscriber_dictionary.stop_cam(v)
|
|
||||||
if rospy.core.is_shutdown():
|
|
||||||
raise rospy.exceptions.ROSInterruptException("rospy shutdown")
|
|
||||||
|
46
displayarray/frame/zmq_to_opencv.py
Normal file
46
displayarray/frame/zmq_to_opencv.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import cv2
|
||||||
|
import zmq
|
||||||
|
from tensorcom.tenbin import decode_buffer
|
||||||
|
|
||||||
|
class ZmqCam(object):
|
||||||
|
"""Add OpenCV camera controls to a numpy array."""
|
||||||
|
|
||||||
|
def __init__(self, img):
|
||||||
|
"""Create a fake camera for OpenCV based on the initial array."""
|
||||||
|
assert isinstance(img, str)
|
||||||
|
s = img.split('#')
|
||||||
|
self.__ctx = zmq.Context()
|
||||||
|
self.__addr = s[0]
|
||||||
|
self.__sub = self.__ctx.socket(zmq.SUB)
|
||||||
|
if len(s)>1:
|
||||||
|
self.__topic = bytes(s[1], 'ascii')
|
||||||
|
self.__sub.setsockopt(zmq.SUBSCRIBE, self.__topic)
|
||||||
|
else:
|
||||||
|
self.__topic = b""
|
||||||
|
self.__sub.connect(self.__addr)
|
||||||
|
|
||||||
|
self.__is_opened = True
|
||||||
|
|
||||||
|
def set(self, *args, **kwargs):
|
||||||
|
"""Set CAP_PROP_FRAME_WIDTH or CAP_PROP_FRAME_HEIGHT to scale a numpy array to that size."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(*args, **kwargs):
|
||||||
|
"""Get OpenCV args. Currently only a fake CAP_PROP_FRAME_COUNT to fix detecting video ends."""
|
||||||
|
if args[0] == cv2.CAP_PROP_FRAME_COUNT:
|
||||||
|
return float("inf")
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
"""Read back the numpy array in standard "did it work", "the array", OpenCV format."""
|
||||||
|
r = self.__sub.recv_multipart()
|
||||||
|
arrs = decode_buffer(r[1])[0]
|
||||||
|
return True, arrs
|
||||||
|
|
||||||
|
def isOpened(self): # NOSONAR
|
||||||
|
"""Hack to tell OpenCV we're opened until we call release."""
|
||||||
|
return self.__is_opened
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
"""Let OpenCV know we're finished."""
|
||||||
|
self.__is_opened = False
|
@ -29,6 +29,12 @@ try:
|
|||||||
except:
|
except:
|
||||||
get_bus_info_from_camera = None
|
get_bus_info_from_camera = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import zmq
|
||||||
|
from tensorcom.tenbin import encode_buffer
|
||||||
|
except:
|
||||||
|
warnings.warn("Could not import ZMQ and tensorcom. Cannot send messages between programs.")
|
||||||
|
|
||||||
|
|
||||||
class SubscriberWindows(object):
|
class SubscriberWindows(object):
|
||||||
"""Windows that subscribe to updates to cameras, videos, and arrays."""
|
"""Windows that subscribe to updates to cameras, videos, and arrays."""
|
||||||
@ -52,6 +58,9 @@ class SubscriberWindows(object):
|
|||||||
self.input_cams: List[str] = []
|
self.input_cams: List[str] = []
|
||||||
self.exited = False
|
self.exited = False
|
||||||
self.silent = silent
|
self.silent = silent
|
||||||
|
self.ctx = None
|
||||||
|
self.sock_list = []
|
||||||
|
self.top_list = []
|
||||||
|
|
||||||
if callbacks is None:
|
if callbacks is None:
|
||||||
callbacks = []
|
callbacks = []
|
||||||
@ -237,6 +246,11 @@ class SubscriberWindows(object):
|
|||||||
self.update_frames()
|
self.update_frames()
|
||||||
msg_cmd = sub_cmd.get()
|
msg_cmd = sub_cmd.get()
|
||||||
key = self.handle_keys(cv2.waitKey(1))
|
key = self.handle_keys(cv2.waitKey(1))
|
||||||
|
if self.sock_list:
|
||||||
|
for s, t in zip(self.sock_list, self.top_list):
|
||||||
|
f = list(self.frames.values())
|
||||||
|
if f:
|
||||||
|
s.send_multipart([t, encode_buffer(f[0])])
|
||||||
return msg_cmd, key
|
return msg_cmd, key
|
||||||
|
|
||||||
def wait_for_init(self):
|
def wait_for_init(self):
|
||||||
@ -277,6 +291,13 @@ class SubscriberWindows(object):
|
|||||||
window_commands.quit(force_all_read=False)
|
window_commands.quit(force_all_read=False)
|
||||||
self.__stop_all_cams()
|
self.__stop_all_cams()
|
||||||
|
|
||||||
|
def publish_to(self, address, topic=b""):
|
||||||
|
if self.ctx==None:
|
||||||
|
self.ctx = zmq.Context()
|
||||||
|
self.sock_list.append(self.ctx.socket(zmq.PUB))
|
||||||
|
self.sock_list[-1].bind(address)
|
||||||
|
self.top_list.append(topic)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cams(self):
|
def cams(self):
|
||||||
"""Get the camera instances. Can be used for OpenCV or V4L2 functions, depending on backend."""
|
"""Get the camera instances. Can be used for OpenCV or V4L2 functions, depending on backend."""
|
||||||
@ -432,3 +453,20 @@ def breakpoint_display(*args, **kwargs):
|
|||||||
def read_updates(*args, **kwargs):
|
def read_updates(*args, **kwargs):
|
||||||
"""Read back all frame updates and yield a list of frames. List is empty if no frames were read."""
|
"""Read back all frame updates and yield a list of frames. List is empty if no frames were read."""
|
||||||
return display(*args, **kwargs, silent=True)
|
return display(*args, **kwargs, silent=True)
|
||||||
|
|
||||||
|
|
||||||
|
def publish_updates(*args, address="tcp://127.0.0.1:7880", topic=b"", **kwargs):
|
||||||
|
"""Publish all the updates to the given address and topic"""
|
||||||
|
if kwargs['blocking']:
|
||||||
|
block = True
|
||||||
|
kwargs['blocking'] = False
|
||||||
|
else:
|
||||||
|
block = False
|
||||||
|
|
||||||
|
r = read_updates(*args, **kwargs)
|
||||||
|
r.publish_to(address, topic)
|
||||||
|
|
||||||
|
if block:
|
||||||
|
r.loop()
|
||||||
|
for vt in r.close_threads:
|
||||||
|
vt.join()
|
||||||
|
@ -21,7 +21,7 @@ i = 0
|
|||||||
while d:
|
while d:
|
||||||
if len(d.frames) > 0:
|
if len(d.frames) > 0:
|
||||||
i += 1
|
i += 1
|
||||||
frame = d.frames[0]
|
frame = next(iter(d.frames.values()))
|
||||||
center_sin = [(m.sin(m.pi * (i / 70.0))), (m.cos(m.pi * (i / 120.0)))]
|
center_sin = [(m.sin(m.pi * (i / 70.0))), (m.cos(m.pi * (i / 120.0)))]
|
||||||
pre_crop_callback.center = [
|
pre_crop_callback.center = [
|
||||||
center_sin[0] * 720 / 2 + 720 / 2,
|
center_sin[0] * 720 / 2 + 720 / 2,
|
||||||
|
0
examples/networking/__init__.py
Normal file
0
examples/networking/__init__.py
Normal file
15
examples/networking/get.py
Normal file
15
examples/networking/get.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import zmq
|
||||||
|
from displayarray import display
|
||||||
|
from tensorcom.tenbin import decode_buffer
|
||||||
|
|
||||||
|
ctx = zmq.Context()
|
||||||
|
s = ctx.socket(zmq.SUB)
|
||||||
|
s.setsockopt(zmq.SUBSCRIBE, b"topic")
|
||||||
|
s.connect("tcp://127.0.0.1:7880")
|
||||||
|
|
||||||
|
d = display()
|
||||||
|
while True:
|
||||||
|
r = s.recv_multipart()
|
||||||
|
# r[0]=="topic"
|
||||||
|
arr = decode_buffer(r[1])
|
||||||
|
d.update(arr[0], '0')
|
10
examples/networking/get_easy.py
Normal file
10
examples/networking/get_easy.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from displayarray import display
|
||||||
|
import time
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
for up in display("tcp://127.0.0.1:7880#topic"):
|
||||||
|
if up:
|
||||||
|
t1 = time.time()
|
||||||
|
u = next(iter(up.values()))[0]
|
||||||
|
print(1.0 / (t1 - t0))
|
||||||
|
t0 = t1
|
26
examples/networking/to.py
Normal file
26
examples/networking/to.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from displayarray import read_updates
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
from tensorcom.tenbin import encode_buffer
|
||||||
|
|
||||||
|
def black_and_white(arr):
|
||||||
|
return (np.sum(arr, axis=-1) / 3).astype(np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
t0 = t1 = time.time()
|
||||||
|
|
||||||
|
ctx = zmq.Context()
|
||||||
|
s = ctx.socket(zmq.PUB)
|
||||||
|
s.bind("tcp://127.0.0.1:7880")
|
||||||
|
|
||||||
|
for up in read_updates(0, size=(9999,9999)):
|
||||||
|
if up:
|
||||||
|
t1 = time.time()
|
||||||
|
u = next(iter(up.values()))[0]
|
||||||
|
s.send_multipart([b'topic', encode_buffer([u])])
|
||||||
|
print(1.0 / (t1 - t0))
|
||||||
|
t0 = t1
|
||||||
|
|
||||||
|
|
9
examples/networking/to_easy.py
Normal file
9
examples/networking/to_easy.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from displayarray import publish_updates
|
||||||
|
|
||||||
|
publish_updates(
|
||||||
|
0,
|
||||||
|
size=(9999, 9999),
|
||||||
|
address="tcp://127.0.0.1:7880",
|
||||||
|
topic=b'topic',
|
||||||
|
blocking=True
|
||||||
|
)
|
3
setup.py
3
setup.py
@ -44,7 +44,8 @@ setup(
|
|||||||
"localpubsub==0.0.4",
|
"localpubsub==0.0.4",
|
||||||
"numpy>=1.17.0",
|
"numpy>=1.17.0",
|
||||||
"opencv-python==4.*,>=4.0.0",
|
"opencv-python==4.*,>=4.0.0",
|
||||||
"pyzmq==18.1.0",
|
"pyzmq>=22.0.3",
|
||||||
|
"tensorcom"
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
# pypi doesn't allow direct dependencies for security reasons,
|
# pypi doesn't allow direct dependencies for security reasons,
|
||||||
|
Reference in New Issue
Block a user