Merge pull request #26 from SimLeek/fixup

Fixup
This commit is contained in:
2019-09-30 23:06:16 -07:00
committed by GitHub
35 changed files with 904 additions and 754 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
[run]
source =
cvpubsubs
displayarray
tests
branch = True
omit =
cvpubsubs/cli.py
displayarray/cli.py
[report]
exclude_lines =
-2
View File
@@ -4,14 +4,12 @@ sudo: true
cache: pip
python:
- '2.7'
- '3.5'
- '3.6'
- '3.7'
install:
- pip install -r requirements.txt
script:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then export CIBW_BUILD='cp27*'; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 ]]; then export CIBW_BUILD='cp35*'; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then export CIBW_BUILD='cp36*'; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then export CIBW_BUILD='cp37*'; fi
+17 -17
View File
@@ -1,26 +1,26 @@
# CVPubSubs
# displayarray
A threaded PubSub OpenCV interfaceREADME.md. Webcam and video feeds to multiple windows is supported.
## Installation
CVPubSubs is distributed on `PyPI <https://pypi.org>`_ as a universal
displayarray is distributed on `PyPI <https://pypi.org>`_ as a universal
wheel and is available on Linux/macOS and Windows and supports
Python 2.7/3.5+ and PyPy.
$ pip install CVPubSubs
$ pip install displayarray
## Usage
### Video Editing and Publishing
#### Display your webcam
import cvpubsubs.webcam_pub as w
import displayarray.webcam_pub as w
w.VideoHandlerThread().display()
#### Change Display Arguments
import cvpubsubs.webcam_pub as w
import displayarray.webcam_pub as w
video_thread = w.VideoHandlerThread(video_source=0,
callbacks = w.display_callbacks,
@@ -30,8 +30,8 @@ Python 2.7/3.5+ and PyPy.
)
#### handle mouse input
import cvpubsubs.webcam_pub as w
from cvpubsubs.input import mouse_loop
import displayarray.webcam_pub as w
from displayarray.input import mouse_loop
@mouse_loop
def print_mouse(mouse_event):
@@ -40,8 +40,8 @@ Python 2.7/3.5+ and PyPy.
w.VideoHandlerThread().display()
#### take in key input
import cvpubsubs.webcam_pub as w
from cvpubsubs.input import key_loop
import displayarray.webcam_pub as w
from displayarray.input import key_loop
@key_loop
def print_key_thread(key_chr):
@@ -50,7 +50,7 @@ Python 2.7/3.5+ and PyPy.
w.VideoHandlerThread().display()
#### Run your own functions on the frames
import cvpubsubs.webcam_pub as w
import displayarray.webcam_pub as w
def redden_frame_print_spam(frame, cam_id):
frame[:, :, 0] = 0
@@ -70,8 +70,8 @@ Python 2.7/3.5+ and PyPy.
t.display()
#### Display multiple windows from one source
import cvpubsubs.webcam_pub as w
from cvpubsubs.window_sub import SubscriberWindows
import displayarray.webcam_pub as w
from displayarray.window_sub import SubscriberWindows
def cam_handler(frame, cam_id):
SubscriberWindows.set_global_frame_dict(cam_id, frame, frame)
@@ -91,8 +91,8 @@ Python 2.7/3.5+ and PyPy.
t.join()
#### Display multiple windows from multiple sources
iport cvpubsubs.webcam_pub as w
from cvpubsubs.window_sub import SubscriberWindows
iport displayarray.webcam_pub as w
from displayarray.window_sub import SubscriberWindows
t1 = w.VideoHandlerThread(0)
t2 = w.VideoHandlerThread(1)
@@ -108,8 +108,8 @@ Python 2.7/3.5+ and PyPy.
t1.join()
#### Run a function on each pixel
from cvpubsubs.webcam_pub import VideoHandlerThread
from cvpubsubs.webcam_pub.callbacks import function_display_callback
from displayarray.webcam_pub import VideoHandlerThread
from displayarray.webcam_pub.callbacks import function_display_callback
img = np.zeros((50, 50, 1))
img[0:5, 0:5, :] = 1
@@ -130,7 +130,7 @@ Python 2.7/3.5+ and PyPy.
## License
CVPubSubs is distributed under the terms of both
displayarray is distributed under the terms of both
- `MIT License <https://choosealicense.com/licenses/mit>`_
- `Apache License, Version 2.0 <https://choosealicense.com/licenses/apache-2.0>`_
+1 -1
View File
@@ -1,2 +1,2 @@
# redirection, so we can use subtree like pip
from cvpubsubs import webcam_pub, window_sub
from displayarray import frame_publising, subscriber_window
-3
View File
@@ -1,3 +0,0 @@
__version__ = '0.6.5'
from .window_sub.cv_window_sub import display
-67
View File
@@ -1,67 +0,0 @@
from cvpubsubs.window_sub.winctrl import WinCtrl
import numpy as np
if False:
from typing import Union
def global_cv_display_callback(frame, # type: np.ndarray
cam_id # type: Union[int, str]
):
from cvpubsubs.window_sub import SubscriberWindows
"""Default callback for sending frames to the global frame dictionary.
:param frame: The video or image frame
:type frame: np.ndarray
:param cam_id: The video or image source
:type cam_id: Union[int, str]
"""
SubscriberWindows.frame_dict[str(cam_id) + "frame"] = frame
class function_display_callback(object): # NOSONAR
def __init__(self, display_function, finish_function=None):
"""Used for running arbitrary functions on pixels.
>>> import random
>>> from cvpubsubs.webcam_pub import VideoHandlerThread
>>> img = np.zeros((300, 300, 3))
>>> def fun(array, coords, finished):
... r,g,b = random.random()/20.0, random.random()/20.0, random.random()/20.0
... array[coords[0:2]] = (array[coords[0:2]] + [r,g,b])%1.0
>>> VideoHandlerThread(video_source=img, callbacks=function_display_callback(fun)).display()
:param display_function: a function to run on the input image.
:param finish_function: a function to run on the input image when the other function finishes.
"""
self.looping = True
self.first_call = True
def _run_finisher(self, frame, finished, *args, **kwargs):
if not callable(finish_function):
WinCtrl.quit()
else:
finished = finish_function(frame, Ellipsis, finished, *args, **kwargs)
if finished:
WinCtrl.quit()
def _display_internal(self, frame, *args, **kwargs):
finished = True
if self.first_call:
# return to display initial frame
self.first_call = False
return
if self.looping:
it = np.nditer(frame, flags=['multi_index'])
while not it.finished:
x, y, c = it.multi_index
finished = display_function(frame, (x, y, c), finished, *args, **kwargs)
it.iternext()
if finished:
self.looping = False
_run_finisher(self, frame, finished, *args, **kwargs)
self.inner_function = _display_internal
def __call__(self, *args, **kwargs):
return self.inner_function(self, *args, **kwargs)
-95
View File
@@ -1,95 +0,0 @@
from cvpubsubs.window_sub.winctrl import WinCtrl
import threading
import time
if False:
from typing import Callable
from cvpubsubs.window_sub.mouse_event import MouseEvent
class mouse_thread(object): # NOSONAR
def __init__(self, f):
self.f = f
self.sub_mouse = WinCtrl.mouse_pub.make_sub()
def __call__(self, *args, **kwargs):
self.f(self.sub_mouse, *args, **kwargs)
class mouse_loop_thread(object): # NOSONAR
def __init__(self, f, run_when_no_events=False, fps=60):
self.f = f
self.sub_mouse = WinCtrl.mouse_pub.make_sub()
self.sub_cmd = WinCtrl.win_cmd_pub.make_sub()
self.sub_cmd.return_on_no_data = ''
self.run_when_no_events = run_when_no_events
self.fps = fps
def __call__(self, *args, **kwargs):
msg_cmd = ''
while msg_cmd != 'quit':
mouse_xyzclick = self.sub_mouse.get(blocking=True) # type: MouseEvent
if mouse_xyzclick is not self.sub_mouse.return_on_no_data:
self.f(mouse_xyzclick, *args, **kwargs)
elif self.run_when_no_events:
self.f(None, *args, **kwargs)
msg_cmd = self.sub_cmd.get()
time.sleep(1.0 / self.fps)
WinCtrl.quit(force_all_read=False)
class mouse_loop(object): # NOSONAR
def __init__(self, f, run_when_no_events=False):
self.t = threading.Thread(target=mouse_loop_thread(f, run_when_no_events))
self.t.start()
def __call__(self, *args, **kwargs):
return self.t
class key_thread(object): # NOSONAR
def __init__(self, f):
self.f = f
self.sub_key = WinCtrl.key_pub.make_sub()
def __call__(self, *args, **kwargs):
self.f(self.sub_key, *args, **kwargs)
class key_loop_thread(object): # NOSONAR
def __init__(self, f, run_when_no_events=False, fps=60):
self.f = f
self.sub_key = WinCtrl.key_pub.make_sub()
self.sub_cmd = WinCtrl.win_cmd_pub.make_sub()
self.sub_cmd.return_on_no_data = ''
self.run_when_no_events = run_when_no_events
self.fps = fps
def __call__(self, *args, **kwargs):
msg_cmd = ''
while msg_cmd != 'quit':
key_chr = self.sub_key.get() # type: chr
if key_chr is not self.sub_key.return_on_no_data:
self.f(key_chr, *args, **kwargs)
elif self.run_when_no_events:
self.f(None, *args, **kwargs)
msg_cmd = self.sub_cmd.get()
time.sleep(1.0 / self.fps)
WinCtrl.quit(force_all_read=False)
class key_loop(object): # NOSONAR
def __init__(self,
f, # type: Callable[[chr],None]
run_when_no_events=False):
self.t = threading.Thread(target=key_loop_thread(f, run_when_no_events))
self.t.start()
def __call__(self, *args, **kwargs):
return self.t
-12
View File
@@ -1,12 +0,0 @@
import numpy as np
from collections import Hashable
def uid_for_source(video_source):
if len(str(video_source)) <= 1000:
uid = str(video_source)
elif isinstance(video_source, Hashable):
uid = str(hash(video_source))
else:
uid = str(hash(str(video_source)))
return uid
-5
View File
@@ -1,5 +0,0 @@
from .camctrl import CamCtrl
from .frame_handler import VideoHandlerThread, display_callbacks
from .get_cam_ids import get_cam_ids
from .pub_cam import pub_cam_thread
from .np_cam import NpCam
-68
View File
@@ -1,68 +0,0 @@
from threading import Lock
from localpubsub import VariablePub, VariableSub
if False:
from typing import Union, Dict
class CamHandler(object):
def __init__(self, name, sub):
self.name = name
self.cmd = None
self.sub = sub # type: VariableSub
self.pub = VariablePub()
self.cmd_pub = VariablePub()
class Cam(object):
def __init__(self, name):
self.name = name
self.cmd = None
self.frame_pub = VariablePub()
self.cmd_pub = VariablePub()
self.status_pub = VariablePub()
class CamCtrl(object):
cv_cam_handlers_dict = {} # type: Dict[str, CamHandler]
cv_cams_dict = {} # type: Dict[str, Cam]
@staticmethod
def register_cam(cam_id):
cam = Cam(str(cam_id))
CamCtrl.cv_cams_dict[str(cam_id)] = cam
CamCtrl.cv_cam_handlers_dict[str(cam_id)] = CamHandler(str(cam_id), cam.frame_pub.make_sub())
@staticmethod
def stop_cam(cam_id # type: Union[int, str]
):
CamCtrl.cv_cams_dict[str(cam_id)].cmd_pub.publish('quit', blocking=True)
CamCtrl.cv_cam_handlers_dict[str(cam_id)].cmd_pub.publish('quit', blocking=True)
@staticmethod
def cam_cmd_sub(cam_id, blocking=True):
if blocking:
while cam_id not in CamCtrl.cv_cams_dict:
continue
return CamCtrl.cv_cams_dict[str(cam_id)].cmd_pub.make_sub()
@staticmethod
def cam_frame_sub(cam_id, blocking=True):
if blocking:
while cam_id not in CamCtrl.cv_cams_dict:
continue
return CamCtrl.cv_cams_dict[str(cam_id)].frame_pub.make_sub()
@staticmethod
def cam_status_sub(cam_id, blocking=True):
if blocking:
while cam_id not in CamCtrl.cv_cams_dict:
continue
return CamCtrl.cv_cams_dict[str(cam_id)].status_pub.make_sub()
@staticmethod
def handler_cmd_sub(cam_id, blocking=True):
if blocking:
while cam_id not in CamCtrl.cv_cam_handlers_dict:
continue
return CamCtrl.cv_cam_handlers_dict[str(cam_id)].cmd_pub.make_sub()
-101
View File
@@ -1,101 +0,0 @@
import threading
import numpy as np
from cvpubsubs.webcam_pub.pub_cam import pub_cam_thread
from cvpubsubs.webcam_pub.camctrl import CamCtrl
from cvpubsubs.window_sub.winctrl import WinCtrl
from cvpubsubs.serialize import uid_for_source
if False:
from typing import Union, Tuple, Any, Callable, List, Optional
FrameCallable = Callable[[np.ndarray, int], Optional[np.ndarray]]
from cvpubsubs.callbacks import global_cv_display_callback
display_callbacks = [global_cv_display_callback]
class VideoHandlerThread(threading.Thread):
"Thread for publishing frames from a video source."
def __init__(self, video_source=0, # type: Union[int, str, np.ndarray]
callbacks=(), # type: Union[List[FrameCallable], FrameCallable]
request_size=(-1, -1), # type: Tuple[int, int]
high_speed=True, # type: bool
fps_limit=240 # type: float
):
"""Sets up the main thread loop.
:param video_source: The video or image source. Integers typically access webcams, while strings access files.
:type video_source: Union[int, str]
:param callbacks: A list of operations to be performed on every frame, including publishing.
:type callbacks: List[Callable[[np.ndarray, int], Any]]
:param request_size: Requested video size. Actual size may vary, since this is requesting from the hardware.
:type request_size: Tuple[int, int]
:param high_speed: If true, use compression to increase speed.
:type high_speed: bool
:param fps_limit: Limits frames per second.
:type fps_limit: float
"""
super(VideoHandlerThread, self).__init__(target=self.loop, args=())
self.cam_id = uid_for_source(video_source)
self.video_source = video_source
if callable(callbacks):
self.callbacks = [callbacks]
else:
self.callbacks = callbacks
self.request_size = request_size
self.high_speed = high_speed
self.fps_limit = fps_limit
self.exception_raised = None
def loop(self):
"""Continually gets frames from the video publisher, runs callbacks on them, and listens to commands."""
t = pub_cam_thread(self.video_source, self.request_size, self.high_speed, self.fps_limit)
while str(self.cam_id) not in CamCtrl.cv_cams_dict:
continue
sub_cam = CamCtrl.cam_frame_sub(str(self.cam_id))
sub_owner = CamCtrl.handler_cmd_sub(str(self.cam_id))
msg_owner = sub_owner.return_on_no_data = ''
while msg_owner != 'quit':
frame = sub_cam.get(blocking=True, timeout=1.0) # type: np.ndarray
if frame is not None:
frame_c = None
for c in self.callbacks:
try:
frame_c = c(frame)
except TypeError as te:
raise TypeError("Callback functions for cvpubsub need to accept two arguments: array and uid")
except Exception as e:
self.exception_raised = e
frame = frame_c = self.exception_raised
CamCtrl.stop_cam(self.cam_id)
WinCtrl.quit()
raise e
if frame_c is not None:
global_cv_display_callback(frame_c, self.cam_id)
else:
global_cv_display_callback(frame, self.cam_id)
msg_owner = sub_owner.get()
sub_owner.release()
sub_cam.release()
CamCtrl.stop_cam(self.cam_id)
t.join()
def display(self,
callbacks=() # type: List[Callable[[List[np.ndarray]], Any]]
):
from cvpubsubs.window_sub import SubscriberWindows
"""Default display operation. For multiple video sources, please use something outside of this class.
:param callbacks: List of callbacks to be run on frames before displaying to the screen.
:type callbacks: List[Callable[[List[np.ndarray]], Any]]
"""
self.start()
SubscriberWindows(video_sources=[self.cam_id], callbacks=callbacks).loop()
self.join()
if self.exception_raised is not None:
raise self.exception_raised
-1
View File
@@ -1 +0,0 @@
from .cv_window_sub import SubscriberWindows
-200
View File
@@ -1,200 +0,0 @@
import warnings
import cv2
import numpy as np
from .winctrl import WinCtrl
from cvpubsubs.webcam_pub.camctrl import CamCtrl
from cvpubsubs.webcam_pub.frame_handler import VideoHandlerThread
from localpubsub import NoData
from cvpubsubs.window_sub.mouse_event import MouseEvent
from cvpubsubs.serialize import uid_for_source
from typing import List, Union, Callable, Any, Dict
import numpy as np
from cvpubsubs.callbacks import global_cv_display_callback
class SubscriberWindows(object):
frame_dict = {}
esc_key_codes = [27] # ESC key on most keyboards
def __init__(self,
window_names=('cvpubsubs',), # type: List[str]
video_sources=(0,), # type: List[Union[str,int]]
callbacks=(None,), # type: List[Callable[[List[np.ndarray]], Any]]
):
self.source_names = []
self.close_threads = None
self.frames = []
self.input_vid_global_names = []
self.window_names = []
self.input_cams = []
for name in video_sources:
self.add_source(name)
self.callbacks = callbacks
for name in window_names:
self.add_window(name)
def add_source(self, name):
uid = uid_for_source(name)
self.source_names.append(uid)
self.input_vid_global_names.append(uid + "frame")
self.input_cams.append(name)
def add_window(self, name):
self.window_names.append(name)
cv2.namedWindow(name + " (press ESC to quit)")
cv2.setMouseCallback(name + " (press ESC to quit)", self.handle_mouse)
def add_callback(self, callback):
self.callbacks.append(callback)
@staticmethod
def set_global_frame_dict(name, *args):
if len(str(name)) <= 1000:
SubscriberWindows.frame_dict[str(name) + "frame"] = list(args)
elif isinstance(name, np.ndarray):
SubscriberWindows.frame_dict[str(hash(str(name))) + "frame"] = list(args)
else:
raise ValueError("Input window name too long.")
def __stop_all_cams(self):
for c in self.source_names:
CamCtrl.stop_cam(c)
def handle_keys(self,
key_input, # type: int
):
if key_input in self.esc_key_codes:
for name in self.window_names:
cv2.destroyWindow(name + " (press ESC to quit)")
WinCtrl.quit()
self.__stop_all_cams()
return 'quit'
elif key_input not in [-1, 0]:
try:
WinCtrl.key_pub.publish(chr(key_input))
except ValueError:
warnings.warn(
RuntimeWarning("Unknown key code: [{}]. Please report to cv_pubsubs issue page.".format(key_input))
)
def handle_mouse(self, event, x, y, flags, param):
mousey = MouseEvent(event, x, y, flags, param)
WinCtrl.mouse_pub.publish(mousey)
def _display_frames(self, frames, win_num, ids=None):
if isinstance(frames, Exception):
raise frames
for f in range(len(frames)):
# detect nested:
if isinstance(frames[f], (list, tuple)) or frames[f].dtype.num == 17 or len(frames[f].shape) > 3:
win_num = self._display_frames(frames[f], win_num, ids)
else:
cv2.imshow(self.window_names[win_num % len(self.window_names)] + " (press ESC to quit)", frames[f])
win_num += 1
return win_num
def update_window_frames(self):
win_num = 0
for i in range(len(self.input_vid_global_names)):
if self.input_vid_global_names[i] in self.frame_dict and \
not isinstance(self.frame_dict[self.input_vid_global_names[i]], NoData):
if len(self.callbacks) > 0 and self.callbacks[i % len(self.callbacks)] is not None:
self.frames = self.callbacks[i % len(self.callbacks)](
self.frame_dict[self.input_vid_global_names[i]])
else:
self.frames = self.frame_dict[self.input_vid_global_names[i]]
if isinstance(self.frames, np.ndarray) and len(self.frames.shape) <= 3:
self.frames = [self.frames]
win_num = self._display_frames(self.frames, win_num)
def update(self, arr=None, id=None):
if arr is not None and id is not None:
global_cv_display_callback(arr, id)
if id not in self.input_cams:
self.add_source(id)
self.add_window(id)
sub_cmd = WinCtrl.win_cmd_sub()
self.update_window_frames()
msg_cmd = sub_cmd.get()
key = self.handle_keys(cv2.waitKey(1))
return msg_cmd, key
def wait_for_init(self):
msg_cmd=""
key = ""
while msg_cmd != 'quit' and key != 'quit' and len(self.frames)==0:
msg_cmd, key = self.update()
def end(self):
if self.close_threads is not None:
for t in self.close_threads:
t.join()
def __exit__(self, exc_type, exc_val, exc_tb):
self.end()
# todo: figure out how to get the red x button to work. Try: https://stackoverflow.com/a/37881722/782170
def loop(self):
sub_cmd = WinCtrl.win_cmd_sub()
msg_cmd = ''
key = ''
while msg_cmd != 'quit' and key != 'quit':
msg_cmd, key = self.update()
sub_cmd.release()
WinCtrl.quit(force_all_read=False)
self.__stop_all_cams()
from cvpubsubs.callbacks import global_cv_display_callback
from threading import Thread
def display(*vids,
callbacks: Union[Dict[Any, Callable], List[Callable], Callable, None] = None,
window_names=[],
blocking=False):
vid_threads = []
if isinstance(callbacks, Dict):
for v in vids:
v_name = uid_for_source(v)
v_callbacks = []
if v_name in callbacks:
v_callbacks.extend(callbacks[v_name])
if v in callbacks:
v_callbacks.extend(callbacks[v])
vid_threads.append(VideoHandlerThread(v, callbacks=v_callbacks))
elif isinstance(callbacks, List):
for v in vids:
vid_threads.append(VideoHandlerThread(v, callbacks=callbacks))
elif isinstance(callbacks, Callable):
for v in vids:
vid_threads.append(VideoHandlerThread(v, callbacks=[callbacks]))
else:
for v in vids:
vid_threads.append(VideoHandlerThread(v))
for v in vid_threads:
v.start()
if len(window_names) == 0:
window_names = ["window {}".format(i) for i in range(len(vids))]
if blocking:
SubscriberWindows(window_names=window_names,
video_sources=vids
).loop()
for v in vid_threads:
v.join()
else:
s = SubscriberWindows(window_names=window_names,
video_sources=vids
)
s.close_threads = vid_threads
v_names = []
for v in vids:
v_name = uid_for_source(v)
v_names.append(v_name)
return s, v_names
-13
View File
@@ -1,13 +0,0 @@
class MouseEvent(object):
def __init__(self, event, x, y, flags, param):
self.event = event
self.x = x
self.y = y
self.flags = flags
self.param = param
def __repr__(self):
return self.__str__()
def __str__(self):
return "event:{}\nx,y:{},{}\nflags:{}\nparam:{}\n".format(self.event, self.x, self.y, self.flags, self.param)
-18
View File
@@ -1,18 +0,0 @@
import threading
import logging
from localpubsub import VariablePub, VariableSub
class WinCtrl(object):
key_pub = VariablePub()
mouse_pub = VariablePub()
win_cmd_pub = VariablePub()
@staticmethod
def quit(force_all_read=True):
WinCtrl.win_cmd_pub.publish('quit', force_all_read=force_all_read)
@staticmethod
def win_cmd_sub(): # type: ()->VariableSub
return WinCtrl.win_cmd_pub.make_sub() # type: VariableSub
+9
View File
@@ -0,0 +1,9 @@
"""
Display any array, webcam, or video file.
display is a function that displays these in their own windows.
"""
__version__ = "0.6.6"
from .subscriber_window.subscriber_windows import display
+72
View File
@@ -0,0 +1,72 @@
from displayarray.subscriber_window import window_commands
import numpy as np
from typing import Union
def global_cv_display_callback(frame: np.ndarray, cam_id: Union[int, str]):
"""
Send frames to the global frame dictionary.
:param frame: The video or image frame
:type frame: np.ndarray
:param cam_id: The video or image source
:type cam_id: Union[int, str]
"""
from displayarray.subscriber_window import SubscriberWindows
SubscriberWindows.FRAME_DICT[str(cam_id) + "frame"] = frame
class function_display_callback(object): # NOSONAR
"""
Used for running arbitrary functions on pixels.
>>> import random
>>> from displayarray.webcam_pub import VideoHandlerThread
>>> img = np.zeros((300, 300, 3))
>>> def fun(array, coords, finished):
... r,g,b = random.random()/20.0, random.random()/20.0, random.random()/20.0
... array[coords[0:2]] = (array[coords[0:2]] + [r,g,b])%1.0
>>> VideoHandlerThread(video_source=img, callbacks=function_display_callback(fun)).display()
:param display_function: a function to run on the input image.
:param finish_function: a function to run on the input image when the other function finishes.
"""
def __init__(self, display_function, finish_function=None):
self.looping = True
self.first_call = True
def _run_finisher(self, frame, finished, *args, **kwargs):
if not callable(finish_function):
window_commands.quit()
else:
finished = finish_function(frame, Ellipsis, finished, *args, **kwargs)
if finished:
window_commands.quit()
def _display_internal(self, frame, *args, **kwargs):
finished = True
if self.first_call:
# return to display initial frame
self.first_call = False
return
if self.looping:
it = np.nditer(frame, flags=["multi_index"])
while not it.finished:
x, y, c = it.multi_index
finished = display_function(
frame, (x, y, c), finished, *args, **kwargs
)
it.iternext()
if finished:
self.looping = False
_run_finisher(self, frame, finished, *args, **kwargs)
self.inner_function = _display_internal
def __call__(self, *args, **kwargs):
"""Call the function "function_display_callback" was set up with."""
return self.inner_function(self, *args, **kwargs)
+15
View File
@@ -0,0 +1,15 @@
"""
Handles publishing arrays, videos, and cameras.
CamCtrl handles sending and receiving commands to specific camera (or array/video) publishers
VideoHandlerThread updates the frames for the global displayer, since OpenCV can only update on the main thread
get_cam_ids gets the ids for all cameras that OpenCV can detect
pub_cam_thread continually publishes updates to arrays, videos, and cameras
np_cam simulates numpy arrays as OpenCV cameras
"""
from . import subscriber_dictionary
from .frame_update_thread import VideoHandlerThread
from .get_frame_ids import get_cam_ids
from .np_to_opencv import NpCam
from .frame_publishing import pub_cam_thread
@@ -4,48 +4,50 @@ import time
import cv2
import numpy as np
from cvpubsubs.webcam_pub.camctrl import CamCtrl
from .np_cam import NpCam
from cvpubsubs.serialize import uid_for_source
from displayarray.frame_publising import subscriber_dictionary
from .np_to_opencv import NpCam
from displayarray.uid import uid_for_source
if False:
from typing import Union, Tuple
from typing import Union, Tuple
def pub_cam_loop(cam_id, # type: Union[int, str]
request_size=(1280, 720), # type: Tuple[int, int]
high_speed=False, # type: bool
fps_limit=240 # type: float
): # type: (...)->bool
"""Publishes whichever camera you select to CVCams.<cam_id>.Vid
def pub_cam_loop(
cam_id: Union[int, str],
request_size: Tuple[int, int] = (1280, 720),
high_speed: bool = False,
fps_limit: float = 240,
) -> bool:
"""
Publish whichever camera you select to CVCams.<cam_id>.Vid.
You can send a quit command 'quit' to CVCams.<cam_id>.Cmd
Status information, such as failure to open, will be posted to CVCams.<cam_id>.Status
:param high_speed: Selects mjpeg transferring, which most cameras seem to support, so speed isn't limited
:param fps_limit: Limits the frames per second.
:param cam_id: An integer representing which webcam to use, or a string representing a video file.
:param request_size: A tuple with width, then height, to request the video size.
:return: True if loop ended normally, False if it failed somehow.
"""
name = uid_for_source(cam_id)
if isinstance(cam_id, (int, str)):
cam = cv2.VideoCapture(cam_id)
cam: Union[NpCam, cv2.VideoCapture] = cv2.VideoCapture(cam_id)
elif isinstance(cam_id, np.ndarray):
cam = NpCam(cam_id) # type: NpCam
cam = NpCam(cam_id)
else:
raise TypeError("Only strings or ints representing cameras, or numpy arrays representing pictures supported.")
raise TypeError(
"Only strings or ints representing cameras, or numpy arrays representing pictures supported."
)
CamCtrl.register_cam(name)
subscriber_dictionary.register_cam(name)
# cam.set(cv2.CAP_PROP_CONVERT_RGB, 0)
frame_counter = 0
sub = CamCtrl.cam_cmd_sub(name)
sub.return_on_no_data = ''
msg = ''
sub = subscriber_dictionary.cam_cmd_sub(name)
sub.return_on_no_data = ""
msg = ""
if high_speed:
cam.set(cv2.CAP_PROP_FOURCC, cv2.CAP_OPENCV_MJPEG)
@@ -54,23 +56,23 @@ def pub_cam_loop(cam_id, # type: Union[int, str]
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, request_size[1])
if not cam.isOpened():
CamCtrl.cv_cams_dict[name].status_pub.publish("failed")
subscriber_dictionary.CV_CAMS_DICT[name].status_pub.publish("failed")
return False
now = time.time()
while msg != 'quit':
time.sleep(1. / (fps_limit - (time.time() - now)))
while msg != "quit":
time.sleep(1.0 / (fps_limit - (time.time() - now)))
now = time.time()
(ret, frame) = cam.read() # type: Tuple[bool, np.ndarray ]
if ret is False or not isinstance(frame, np.ndarray):
cam.release()
CamCtrl.cv_cams_dict[name].status_pub.publish("failed")
subscriber_dictionary.CV_CAMS_DICT[name].status_pub.publish("failed")
return False
if cam.get(cv2.CAP_PROP_FRAME_COUNT) > 0:
frame_counter += 1
if frame_counter >= cam.get(cv2.CAP_PROP_FRAME_COUNT):
frame_counter = 0
cam = cv2.VideoCapture(cam_id)
CamCtrl.cv_cams_dict[name].frame_pub.publish(frame)
subscriber_dictionary.CV_CAMS_DICT[name].frame_pub.publish(frame)
msg = sub.get()
sub.release()
@@ -78,12 +80,15 @@ def pub_cam_loop(cam_id, # type: Union[int, str]
return True
def pub_cam_thread(cam_id, # type: Union[int, str]
request_ize=(1280, 720), # type: Tuple[int, int]
high_speed=False, # type: bool
fps_limit=240 # type: float
):
# type: (...) -> threading.Thread
t = threading.Thread(target=pub_cam_loop, args=(cam_id, request_ize, high_speed, fps_limit))
def pub_cam_thread(
cam_id: Union[int, str],
request_ize: Tuple[int, int] = (1280, 720),
high_speed: bool = False,
fps_limit: float = 240,
) -> threading.Thread:
"""Run pub_cam_loop in a new thread."""
t = threading.Thread(
target=pub_cam_loop, args=(cam_id, request_ize, high_speed, fps_limit)
)
t.start()
return t
@@ -0,0 +1,100 @@
import threading
from typing import Union, Tuple, Any, Callable, List, Optional
import numpy as np
from displayarray.callbacks import global_cv_display_callback
from displayarray.uid import uid_for_source
from displayarray.frame_publising import subscriber_dictionary
from displayarray.frame_publising.frame_publishing import pub_cam_thread
from displayarray.subscriber_window import window_commands
FrameCallable = Callable[[np.ndarray], Optional[np.ndarray]]
class VideoHandlerThread(threading.Thread):
"""Thread for publishing frames from a video source."""
def __init__(
self,
video_source: Union[int, str, np.ndarray] = 0,
callbacks: Optional[Union[List[FrameCallable], FrameCallable]] = None,
request_size: Tuple[int, int] = (99999, 99999),
high_speed: bool = True,
fps_limit: float = 240,
):
super(VideoHandlerThread, self).__init__(target=self.loop, args=())
self.cam_id = uid_for_source(video_source)
self.video_source = video_source
if callbacks is None:
callbacks = []
if callable(callbacks):
self.callbacks = [callbacks]
else:
self.callbacks = callbacks
self.request_size = request_size
self.high_speed = high_speed
self.fps_limit = fps_limit
self.exception_raised = None
def __wait_for_cam_id(self):
while str(self.cam_id) not in subscriber_dictionary.CV_CAMS_DICT:
continue
def __apply_callbacks_to_frame(self, frame):
if frame is not None:
frame_c = None
for c in self.callbacks:
try:
frame_c = c(frame)
except TypeError as te:
raise TypeError(
"Callback functions for cvpubsub need to accept two arguments: array and uid"
)
except Exception as e:
self.exception_raised = e
frame = frame_c = self.exception_raised
subscriber_dictionary.stop_cam(self.cam_id)
window_commands.quit()
raise e
if frame_c is not None:
global_cv_display_callback(frame_c, self.cam_id)
else:
global_cv_display_callback(frame, self.cam_id)
def loop(self):
"""Continually get frames from the video publisher, run callbacks on them, and listen to commands."""
t = pub_cam_thread(
self.video_source, self.request_size, self.high_speed, self.fps_limit
)
self.__wait_for_cam_id()
sub_cam = subscriber_dictionary.cam_frame_sub(str(self.cam_id))
sub_owner = subscriber_dictionary.handler_cmd_sub(str(self.cam_id))
msg_owner = sub_owner.return_on_no_data = ""
while msg_owner != "quit":
frame = sub_cam.get(blocking=True, timeout=1.0) # type: np.ndarray
self.__apply_callbacks_to_frame(frame)
msg_owner = sub_owner.get()
sub_owner.release()
sub_cam.release()
subscriber_dictionary.stop_cam(self.cam_id)
t.join()
def display(self, callbacks: List[Callable[[np.ndarray], Any]] = None):
"""
Start default display operation.
For multiple video sources, please use something outside of this class.
:param callbacks: List of callbacks to be run on frames before displaying to the screen.
"""
from displayarray.subscriber_window import SubscriberWindows
if callbacks is None:
callbacks = []
self.start()
SubscriberWindows(video_sources=[self.cam_id], callbacks=callbacks).loop()
self.join()
if self.exception_raised is not None:
raise self.exception_raised
@@ -1,11 +1,11 @@
import cv2
if False:
from typing import List
from typing import List
def get_cam_ids(): # type: () -> List[int]
cam_list = []
def get_cam_ids() -> List[int]:
"""Get all cameras that OpenCV can currently detect."""
cam_list: List[int] = []
while True:
cam = cv2.VideoCapture(len(cam_list))
@@ -3,6 +3,8 @@ import cv2
class NpCam(object):
"""Add OpenCV camera controls to a numpy array."""
def __init__(self, img):
assert isinstance(img, np.ndarray)
self.__img = img
@@ -23,6 +25,7 @@ class NpCam(object):
self.__img = cv2.resize(self.__img, (self.__width, self.__height))
def set(self, *args, **kwargs):
"""Set CAP_PROP_FRAME_WIDTH or CAP_PROP_FRAME_HEIGHT to scale a numpy array to that size."""
if args[0] in [cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT]:
self.__wait_for_ratio = not self.__wait_for_ratio
if args[0] == cv2.CAP_PROP_FRAME_WIDTH:
@@ -34,14 +37,18 @@ class NpCam(object):
@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):
return (True, self.__img)
"""Read back the numpy array in standard "did it work", "the array", OpenCV format."""
return True, self.__img
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
@@ -0,0 +1,78 @@
"""Publisher-subscriber commands to and from the camera."""
from localpubsub import VariablePub, VariableSub
from typing import Union, Dict
class CamHandler(object):
"""A camera handler instance that will send commands to and receive data from a camera."""
def __init__(self, name, sub):
self.name = name
self.cmd = None
self.sub: VariableSub = sub
self.pub = VariablePub()
self.cmd_pub = VariablePub()
class Cam(object):
"""A camera publisher instance that will send frames, status, and commands out."""
def __init__(self, name):
self.name = name
self.cmd = None
self.frame_pub = VariablePub()
self.cmd_pub = VariablePub()
self.status_pub = VariablePub()
CV_CAM_HANDLERS_DICT: Dict[str, CamHandler] = {}
CV_CAMS_DICT: Dict[str, Cam] = {}
def register_cam(cam_id):
"""Register camera "cam_id" to a global list so it can be picked up."""
cam = Cam(str(cam_id))
CV_CAMS_DICT[str(cam_id)] = cam
CV_CAM_HANDLERS_DICT[str(cam_id)] = CamHandler(
str(cam_id), cam.frame_pub.make_sub()
)
def stop_cam(cam_id: Union[int, str]):
"""Tell camera "cam_id" to end it's main loop."""
CV_CAMS_DICT[str(cam_id)].cmd_pub.publish("quit", blocking=True)
CV_CAM_HANDLERS_DICT[str(cam_id)].cmd_pub.publish("quit", blocking=True)
def cam_cmd_sub(cam_id, blocking=True):
"""Get a command subscriber for registered camera "cam_id"."""
if blocking:
while cam_id not in CV_CAMS_DICT:
continue
return CV_CAMS_DICT[str(cam_id)].cmd_pub.make_sub()
def cam_frame_sub(cam_id, blocking=True):
"""Get a frame subscriber for registered camera "cam_id"."""
if blocking:
while cam_id not in CV_CAMS_DICT:
continue
return CV_CAMS_DICT[str(cam_id)].frame_pub.make_sub()
def cam_status_sub(cam_id, blocking=True):
"""Get a status subscriber for registered camera "cam_id"."""
if blocking:
while cam_id not in CV_CAMS_DICT:
continue
return CV_CAMS_DICT[str(cam_id)].status_pub.make_sub()
def handler_cmd_sub(cam_id, blocking=True):
"""Get a command subscriber for registered camera "cam_id" handler."""
if blocking:
while cam_id not in CV_CAM_HANDLERS_DICT:
continue
return CV_CAM_HANDLERS_DICT[str(cam_id)].cmd_pub.make_sub()
+122
View File
@@ -0,0 +1,122 @@
from displayarray.subscriber_window import window_commands
import threading
import time
from typing import Callable
class MouseEvent(object):
"""Holds all the OpenCV mouse event information."""
def __init__(self, event, x, y, flags, param):
self.event = event
self.x = x
self.y = y
self.flags = flags
self.param = param
def __repr__(self):
return self.__str__()
def __str__(self):
return "event:{}\nx,y:{},{}\nflags:{}\nparam:{}\n".format(
self.event, self.x, self.y, self.flags, self.param
)
class _mouse_thread(object): # NOSONAR
"""Run a function on mouse information that is received by the window."""
def __init__(self, f):
self.f = f
self.sub_mouse = window_commands.mouse_pub.make_sub()
def __call__(self, *args, **kwargs):
"""Call the function this was set up with."""
self.f(self.sub_mouse, *args, **kwargs)
class _mouse_loop_thread(object): # NOSONAR
"""Run a function on mouse information that is received by the window, in the main loop."""
def __init__(self, f, run_when_no_events=False, fps=60):
self.f = f
self.sub_mouse = window_commands.mouse_pub.make_sub()
self.sub_cmd = window_commands.win_cmd_pub.make_sub()
self.sub_cmd.return_on_no_data = ""
self.run_when_no_events = run_when_no_events
self.fps = fps
def __call__(self, *args, **kwargs):
"""Run the function this was set up with in a loop."""
msg_cmd = ""
while msg_cmd != "quit":
mouse_xyzclick = self.sub_mouse.get(blocking=True) # type: MouseEvent
if mouse_xyzclick is not self.sub_mouse.return_on_no_data:
self.f(mouse_xyzclick, *args, **kwargs)
elif self.run_when_no_events:
self.f(None, *args, **kwargs)
msg_cmd = self.sub_cmd.get()
time.sleep(1.0 / self.fps)
window_commands.quit(force_all_read=False)
class mouse_loop(object): # NOSONAR
"""Run a function on mouse information that is received by the window, continuously in a new thread."""
def __init__(self, f, run_when_no_events=False):
self.t = threading.Thread(target=_mouse_loop_thread(f, run_when_no_events))
self.t.start()
def __call__(self, *args, **kwargs):
"""Return the thread that was started with the function passed in."""
return self.t
class _key_thread(object): # NOSONAR
"""Run a function on mouse information that is received by the window."""
def __init__(self, f):
self.f = f
self.sub_key = window_commands.key_pub.make_sub()
def __call__(self, *args, **kwargs):
"""Call the function this was set up with."""
self.f(self.sub_key, *args, **kwargs)
class _key_loop_thread(object): # NOSONAR
"""Run a function on mouse information that is received by the window, in the main loop."""
def __init__(self, f, run_when_no_events=False, fps=60):
self.f = f
self.sub_key = window_commands.key_pub.make_sub()
self.sub_cmd = window_commands.win_cmd_pub.make_sub()
self.sub_cmd.return_on_no_data = ""
self.run_when_no_events = run_when_no_events
self.fps = fps
def __call__(self, *args, **kwargs):
"""Run the function this was set up with in a loop."""
msg_cmd = ""
while msg_cmd != "quit":
key_chr = self.sub_key.get() # type: chr
if key_chr is not self.sub_key.return_on_no_data:
self.f(key_chr, *args, **kwargs)
elif self.run_when_no_events:
self.f(None, *args, **kwargs)
msg_cmd = self.sub_cmd.get()
time.sleep(1.0 / self.fps)
window_commands.quit(force_all_read=False)
class key_loop(object): # NOSONAR
"""Run a function on mouse information that is received by the window, continuously in a new thread."""
def __init__(self, f: Callable[[str], None], run_when_no_events=False):
self.t = threading.Thread(target=_key_loop_thread(f, run_when_no_events))
self.t.start()
def __call__(self, *args, **kwargs):
"""Return the thread that was started with the function passed in."""
return self.t
@@ -0,0 +1,7 @@
"""
Displays arrays.
SubscriberWindows displays one array per window, updating it as it's changed.
"""
from .subscriber_windows import SubscriberWindows
@@ -0,0 +1,245 @@
import warnings
from threading import Thread
from typing import List, Union, Callable, Any, Dict, Iterable, Optional
import cv2
import numpy as np
from localpubsub import NoData
from displayarray.callbacks import global_cv_display_callback
from displayarray.uid import uid_for_source
from displayarray.frame_publising import subscriber_dictionary
from displayarray.frame_publising.frame_update_thread import FrameCallable
from displayarray.frame_publising.frame_update_thread import VideoHandlerThread
from displayarray.input import MouseEvent
from displayarray.subscriber_window import window_commands
class SubscriberWindows(object):
"""Windows that subscribe to updates to cameras, videos, and arrays."""
FRAME_DICT: Dict[str, np.ndarray] = {}
ESC_KEY_CODES = [27] # ESC key on most keyboards
def __init__(
self,
window_names: Iterable[str] = ("displayarray",),
video_sources: Iterable[Union[str, int]] = (0,),
callbacks: Optional[List[Callable[[np.ndarray], Any]]] = None,
):
self.source_names: List[Union[str, int]] = []
self.close_threads: Optional[List[Thread]] = None
self.frames: List[np.ndarray] = []
self.input_vid_global_names: List[str] = []
self.window_names: List[str] = []
self.input_cams: List[str] = []
if callbacks is None:
callbacks = []
for name in video_sources:
self.add_source(name)
self.callbacks = callbacks
for name in window_names:
self.add_window(name)
def add_source(self, name):
"""Add another source for this class to display."""
uid = uid_for_source(name)
self.source_names.append(uid)
self.input_vid_global_names.append(uid + "frame")
self.input_cams.append(name)
def add_window(self, name):
"""Add another window for this class to display sources with. The name will be the title."""
self.window_names.append(name)
cv2.namedWindow(name + " (press ESC to quit)")
cv2.setMouseCallback(name + " (press ESC to quit)", self.handle_mouse)
def add_callback(self, callback):
"""Add a callback for this class to apply to videos."""
self.callbacks.append(callback)
def __stop_all_cams(self):
for c in self.source_names:
subscriber_dictionary.stop_cam(c)
def handle_keys(
self, key_input # type: int
):
"""Capture key input for the escape function and passing to key control subscriber threads."""
if key_input in self.ESC_KEY_CODES:
for name in self.window_names:
cv2.destroyWindow(name + " (press ESC to quit)")
window_commands.quit()
self.__stop_all_cams()
return "quit"
elif key_input not in [-1, 0]:
try:
window_commands.key_pub.publish(chr(key_input))
except ValueError:
warnings.warn(
RuntimeWarning(
"Unknown key code: [{}]. Please report to cv_pubsubs issue page.".format(
key_input
)
)
)
def handle_mouse(self, event, x, y, flags, param):
"""Capture mouse input for mouse control subscriber threads."""
mousey = MouseEvent(event, x, y, flags, param)
window_commands.mouse_pub.publish(mousey)
def _display_frames(self, frames, win_num, ids=None):
if isinstance(frames, Exception):
raise frames
for f in range(len(frames)):
# detect nested:
if (
isinstance(frames[f], (list, tuple))
or frames[f].dtype.num == 17
or len(frames[f].shape) > 3
):
win_num = self._display_frames(frames[f], win_num, ids)
else:
cv2.imshow(
self.window_names[win_num % len(self.window_names)]
+ " (press ESC to quit)",
frames[f],
)
win_num += 1
return win_num
def update_window_frames(self):
"""Update the windows with the newest data for all frames."""
win_num = 0
for i in range(len(self.input_vid_global_names)):
if self.input_vid_global_names[i] in self.FRAME_DICT and not isinstance(
self.FRAME_DICT[self.input_vid_global_names[i]], NoData
):
if (
len(self.callbacks) > 0
and self.callbacks[i % len(self.callbacks)] is not None
):
self.frames = self.callbacks[i % len(self.callbacks)](
self.FRAME_DICT[self.input_vid_global_names[i]]
)
else:
self.frames = self.FRAME_DICT[self.input_vid_global_names[i]]
if isinstance(self.frames, np.ndarray) and len(self.frames.shape) <= 3:
self.frames = [self.frames]
win_num = self._display_frames(self.frames, win_num)
def update(self, arr=None, id=None):
"""Update window frames once. Optionally add a new input and input id."""
if arr is not None and id is not None:
global_cv_display_callback(arr, id)
if id not in self.input_cams:
self.add_source(id)
self.add_window(id)
sub_cmd = window_commands.win_cmd_sub()
self.update_window_frames()
msg_cmd = sub_cmd.get()
key = self.handle_keys(cv2.waitKey(1))
return msg_cmd, key
def wait_for_init(self):
"""Update window frames in a loop until they're actually updated. Useful for waiting for cameras to init."""
msg_cmd = ""
key = ""
while msg_cmd != "quit" and key != "quit" and len(self.frames) == 0:
msg_cmd, key = self.update()
def end(self):
"""Close all threads. Should be used with non-blocking mode."""
window_commands.quit(force_all_read=False)
self.__stop_all_cams()
if self.close_threads is not None:
for t in self.close_threads:
t.join()
def __exit__(self, exc_type, exc_val, exc_tb):
self.end()
def loop(self):
"""Continually update window frame. OpenCV only allows this in the main thread."""
sub_cmd = window_commands.win_cmd_sub()
msg_cmd = ""
key = ""
while msg_cmd != "quit" and key != "quit":
msg_cmd, key = self.update()
sub_cmd.release()
window_commands.quit(force_all_read=False)
self.__stop_all_cams()
def _get_video_callback_dict_threads(
*vids, callbacks: Optional[Dict[Any, FrameCallable]] = None
):
assert callbacks is not None
vid_threads = []
for v in vids:
v_name = uid_for_source(v)
v_callbacks: List[Callable[[np.ndarray], Any]] = []
if v_name in callbacks:
v_callbacks.append(callbacks[v_name])
if v in callbacks:
v_callbacks.append(callbacks[v])
vid_threads.append(VideoHandlerThread(v, callbacks=v_callbacks))
return vid_threads
def _get_video_threads(
*vids,
callbacks: Optional[
Union[Dict[Any, FrameCallable], List[FrameCallable], FrameCallable]
] = None
):
vid_threads: List[Thread] = []
if isinstance(callbacks, Dict):
vid_threads = _get_video_callback_dict_threads(*vids, callbacks=callbacks)
elif isinstance(callbacks, List):
for v in vids:
vid_threads.append(VideoHandlerThread(v, callbacks=callbacks))
elif callable(callbacks):
for v in vids:
vid_threads.append(VideoHandlerThread(v, callbacks=[callbacks]))
else:
for v in vids:
if v is not None:
vid_threads.append(VideoHandlerThread(v))
return vid_threads
def display(
*vids,
callbacks: Optional[
Union[Dict[Any, FrameCallable], List[FrameCallable], FrameCallable]
] = None,
window_names=None,
blocking=False
):
"""
Display all the arrays, cameras, and videos passed in.
callbacks can be a dictionary linking functions to videos, or a list of function or functions operating on the video
data before displaying.
Window names end up becoming the title of the windows
"""
vid_threads = _get_video_threads(*vids, callbacks=callbacks)
for v in vid_threads:
v.start()
if window_names is None:
window_names = ["window {}".format(i) for i in range(len(vids))]
if blocking:
SubscriberWindows(window_names=window_names, video_sources=vids).loop()
for v in vid_threads:
v.join()
else:
s = SubscriberWindows(window_names=window_names, video_sources=vids)
s.close_threads = vid_threads
v_names = []
for v in vids:
v_name = uid_for_source(v)
v_names.append(v_name)
return s, v_names
@@ -0,0 +1,15 @@
from localpubsub import VariablePub, VariableSub
key_pub = VariablePub()
mouse_pub = VariablePub()
win_cmd_pub = VariablePub()
def quit(force_all_read=True):
"""Quit the main loop displaying all the windows."""
win_cmd_pub.publish("quit", force_all_read=force_all_read)
def win_cmd_sub() -> VariableSub:
"""Get a subscriber to the main window loop."""
return win_cmd_pub.make_sub()
+18
View File
@@ -0,0 +1,18 @@
from collections.abc import Hashable
def uid_for_source(video_source):
"""Get a uid for any source so it can be passed through the publisher-subscriber system."""
if len(str(video_source)) <= 1000:
uid = str(video_source)
elif isinstance(video_source, Hashable):
try:
uid = str(hash(video_source))
except TypeError:
raise NotImplementedError(
"Displaying immutables filled with mutables is not allowed yet. "
"No tuples of arrays."
)
else:
uid = str(hash(str(video_source)))
return uid
+11
View File
@@ -0,0 +1,11 @@
[mypy]
python_version = 3.7
[mypy-numpy.*]
ignore_missing_imports = True
[mypy-cv2.*]
ignore_missing_imports = True
[mypy-localpubsub.*]
ignore_missing_imports = True
+3 -3
View File
@@ -1,14 +1,14 @@
[metadata]
name = 'CVPubSubs'
name = 'displayarray'
version = '0.0.2'
description = 'Simple tool for working with multiple streams from OpenCV.'
author = 'SimLeek'
author_email = 'josh.miklos@gmail.com'
license = 'MIT/Apache-2.0'
url = 'https://github.com/simleek/CVPubSubs'
url = 'https://github.com/simleek/displayarray'
[requires]
python_version = ['2.7', '3.5', '3.6', 'pypy', 'pypy3']
python_version = ['3.5', '3.6', 'pypy', 'pypy3']
[build-system]
requires = ['setuptools', 'wheel']

Some files were not shown because too many files have changed in this diff Show More