From 09c687ff427b6db3cb621d794a3ed5683b2407a0 Mon Sep 17 00:00:00 2001 From: simleek Date: Tue, 7 Apr 2020 22:06:52 -0700 Subject: [PATCH] removed old async read_updates in favor of silent frame grabbing. Added for loop handling. Set default framerate to infinite. Added no_display example. --- .gitignore | 1 + displayarray/__init__.py | 3 +- displayarray/frame/__init__.py | 2 +- displayarray/frame/frame_publishing.py | 9 ++-- displayarray/frame/frame_updater.py | 6 +-- displayarray/window/subscriber_windows.py | 48 ++++++++++++++------ docs/docsrc/conf.py | 3 +- examples/callbacks/black_and_white.py | 9 +++- examples/looping/no_display.py | 30 +++++++++++++ tests/frame/test_frame_publishing.py | 2 +- tests/frame/test_frame_updater.py | 4 +- tests/window/test_subscriber_windows.py | 53 ++++++++++++++--------- 12 files changed, 119 insertions(+), 51 deletions(-) create mode 100644 examples/looping/no_display.py diff --git a/.gitignore b/.gitignore index 8f7fbc5..fb24077 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ venv.bak/ .idea/ .pytest_cache/ +.coveragerc diff --git a/displayarray/__init__.py b/displayarray/__init__.py index 90a7bca..89d24c1 100644 --- a/displayarray/__init__.py +++ b/displayarray/__init__.py @@ -6,7 +6,6 @@ display is a function that displays these in their own windows. __version__ = "0.7.2" -from .window.subscriber_windows import display, breakpoint_display -from .frame.frame_updater import read_updates +from .window.subscriber_windows import display, breakpoint_display, read_updates from .frame.frame_publishing import publish_updates_zero_mq, publish_updates_ros from . import effects diff --git a/displayarray/frame/__init__.py b/displayarray/frame/__init__.py index 451ab23..5a14093 100644 --- a/displayarray/frame/__init__.py +++ b/displayarray/frame/__init__.py @@ -9,7 +9,7 @@ np_cam simulates numpy arrays as OpenCV cameras """ from . import subscriber_dictionary -from .frame_updater import FrameUpdater, read_updates +from .frame_updater import FrameUpdater from .get_frame_ids import get_cam_ids from .np_to_opencv import NpCam from .frame_publishing import pub_cam_thread diff --git a/displayarray/frame/frame_publishing.py b/displayarray/frame/frame_publishing.py index cbb5ba9..5e4ad68 100644 --- a/displayarray/frame/frame_publishing.py +++ b/displayarray/frame/frame_publishing.py @@ -20,7 +20,7 @@ def pub_cam_loop( cam_id: Union[int, str, np.ndarray], request_size: Tuple[int, int] = (-1, -1), high_speed: bool = True, - fps_limit: float = 240, + fps_limit: float = float("inf"), ) -> bool: """ Publish whichever camera you select to CVCams..Vid. @@ -47,7 +47,6 @@ def pub_cam_loop( subscriber_dictionary.register_cam(name) - # cam.set(cv2.CAP_PROP_CONVERT_RGB, 0) frame_counter = 0 sub = subscriber_dictionary.cam_cmd_sub(name) @@ -89,7 +88,7 @@ def pub_cam_thread( cam_id: Union[int, str], request_ize: Tuple[int, int] = (-1, -1), high_speed: bool = True, - fps_limit: float = 240, + fps_limit: float = float("inf"), ) -> threading.Thread: """Run pub_cam_loop in a new thread. Starts on creation.""" t = threading.Thread( @@ -186,8 +185,8 @@ async def publish_updates_ros( }[dtype] else: msg_type = ( - dtype - ) # allow users to use their own custom messages in numpy arrays + dtype # allow users to use their own custom messages in numpy arrays + ) return msg_type publishers: Dict[str, rospy.Publisher] = {} diff --git a/displayarray/frame/frame_updater.py b/displayarray/frame/frame_updater.py index de14e01..90a9479 100644 --- a/displayarray/frame/frame_updater.py +++ b/displayarray/frame/frame_updater.py @@ -26,7 +26,7 @@ class FrameUpdater(threading.Thread): callbacks: Optional[Union[List[FrameCallable], FrameCallable]] = None, request_size: Tuple[int, int] = (-1, -1), high_speed: bool = True, - fps_limit: float = 240, + fps_limit: float = float("inf"), ): """Create the frame updater thread.""" super(FrameUpdater, self).__init__(target=self.loop, args=()) @@ -115,7 +115,7 @@ class FrameUpdater(threading.Thread): raise self.exception_raised -async def read_updates( +'''async def read_updates( *vids, callbacks: Optional[ Union[ @@ -174,7 +174,7 @@ async def read_updates( for v in vid_names: subscriber_dictionary.stop_cam(v) for v in vid_threads: - v.join() + v.join()''' async def read_updates_zero_mq( diff --git a/displayarray/window/subscriber_windows.py b/displayarray/window/subscriber_windows.py index d054b2d..e345f13 100644 --- a/displayarray/window/subscriber_windows.py +++ b/displayarray/window/subscriber_windows.py @@ -30,6 +30,7 @@ class SubscriberWindows(object): window_names: Iterable[str] = ("displayarray",), video_sources: Iterable[Union[str, int]] = (0,), callbacks: Optional[List[Callable[[np.ndarray], Any]]] = None, + silent: bool = False, ): """Create the array displaying window.""" self.source_names: List[Union[str, int]] = [] @@ -39,14 +40,16 @@ class SubscriberWindows(object): self.window_names: List[str] = [] self.input_cams: List[str] = [] self.exited = False + self.silent = silent 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) + if not self.silent: + for name in window_names: + self.add_window(name) self.update() @@ -54,6 +57,11 @@ class SubscriberWindows(object): self.update() return not self.exited + def __iter__(self): + while not self.exited: + self.update() + yield self.frames + def block(self): """Update the window continuously while blocking the outer program.""" self.loop() @@ -109,7 +117,7 @@ class SubscriberWindows(object): mousey = MouseEvent(event, x, y, flags, param) window_commands.mouse_pub.publish(mousey) - def _display_frames(self, frames, win_num=0, ids=None): + def display_frames(self, frames, win_num=0, ids=None): if isinstance(frames, Exception): raise frames for f in range(len(frames)): @@ -122,7 +130,7 @@ class SubscriberWindows(object): and (len(frames[f].shape) != 3 or frames[f].shape[-1] != 3) ) ): - win_num = self._display_frames(frames[f], win_num, ids) + win_num = self.display_frames(frames[f], win_num, ids) else: if len(self.window_names) <= win_num: self.add_window(str(win_num)) @@ -157,7 +165,7 @@ class SubscriberWindows(object): self.frames[fr] = self.callbacks[-1](self.frames[fr]) break - def update_window_frames(self): + def update_frames(self): """Update the windows with the newest data for all frames.""" self.frames = [] for i in range(len(self.input_vid_global_names)): @@ -172,9 +180,11 @@ class SubscriberWindows(object): frame = c(self.frames[-1]) if frame is not None: self.frames[-1] = frame - self.__check_too_many_channels() + if not self.silent: + self.__check_too_many_channels() self.FRAME_DICT[self.input_vid_global_names[i]] = NoData() - self._display_frames(self.frames) + if not self.silent: + self.display_frames(self.frames) def update(self, arr: np.ndarray = None, id: str = None): """Update window frames once. Optionally add a new input and input id.""" @@ -182,9 +192,10 @@ class SubscriberWindows(object): global_cv_display_callback(arr, id) if id not in self.input_cams: self.add_source(id) - self.add_window(id) + if not self.silent: + self.add_window(id) sub_cmd = window_commands.win_cmd_sub() - self.update_window_frames() + self.update_frames() msg_cmd = sub_cmd.get() key = self.handle_keys(cv2.waitKey(1)) return msg_cmd, key @@ -231,7 +242,7 @@ class SubscriberWindows(object): def _get_video_callback_dict_threads( *vids, callbacks: Optional[Dict[Any, Union[FrameCallable, List[FrameCallable]]]] = None, - fps=240, + fps=float("inf"), size=(-1, -1), ): assert callbacks is not None @@ -264,7 +275,7 @@ def _get_video_threads( FrameCallable, ] ] = None, - fps=240, + fps=float("inf"), size=(-1, -1), ): vid_threads: List[Thread] = [] @@ -300,8 +311,9 @@ def display( ] = None, window_names=None, blocking=False, - fps_limit=240, + fps_limit=float("inf"), size=(-1, -1), + silent=False, ): """ Display all the arrays, cameras, and videos passed in. @@ -318,11 +330,15 @@ def display( 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() + SubscriberWindows( + window_names=window_names, video_sources=vids, silent=silent + ).loop() for vt in vid_threads: vt.join() else: - s = SubscriberWindows(window_names=window_names, video_sources=vids) + s = SubscriberWindows( + window_names=window_names, video_sources=vids, silent=silent + ) s.close_threads = vid_threads return s @@ -330,3 +346,7 @@ def display( def breakpoint_display(*args, **kwargs): """Display all the arrays, cameras, and videos passed in. Stops code execution until the window is closed.""" return display(*args, **kwargs, blocking=True) + + +def read_updates(*args, **kwargs): + return display(*args, **kwargs, silent=True) diff --git a/docs/docsrc/conf.py b/docs/docsrc/conf.py index 11561d4..bf7a52b 100644 --- a/docs/docsrc/conf.py +++ b/docs/docsrc/conf.py @@ -12,7 +12,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath(f'..{os.sep}..')) + +sys.path.insert(0, os.path.abspath(f"..{os.sep}..")) # -- Project information ----------------------------------------------------- diff --git a/examples/callbacks/black_and_white.py b/examples/callbacks/black_and_white.py index 62a530e..f7e050d 100644 --- a/examples/callbacks/black_and_white.py +++ b/examples/callbacks/black_and_white.py @@ -6,4 +6,11 @@ def black_and_white(arr): return (np.sum(arr, axis=-1) / 3).astype(np.uint8) -display(0, callbacks=black_and_white, blocking=True) +import time + +t0 = t1 = time.time() +for up in display(0, size=(1, 1), callbacks=black_and_white): + if up: + t1 = time.time() + print(1.0 / (t1 - t0)) + t0 = t1 diff --git a/examples/looping/no_display.py b/examples/looping/no_display.py new file mode 100644 index 0000000..5d035b8 --- /dev/null +++ b/examples/looping/no_display.py @@ -0,0 +1,30 @@ +from displayarray import read_updates, end_feeds +import time +import cProfile +from examples.videos import test_video + + +def profile_reading(total_seconds=2): + t_init = t01 = time.time() + times = [] + started = False + for up in read_updates(test_video, size=(1, 1)): + if up: + t1 = time.time() + if started: + times.append((t1 - t01) * 1000) + t01 = t1 + started = True + if started: + t2 = time.time() + if t2 - t_init >= total_seconds: + if times: + print(f"Average framerate: {1000 / (sum(times) / len(times))}fps") + else: + print("failure") + break + else: + t_init = time.time() + + +cProfile.run("profile_reading()") diff --git a/tests/frame/test_frame_publishing.py b/tests/frame/test_frame_publishing.py index 840c8b4..1b4c8d0 100644 --- a/tests/frame/test_frame_publishing.py +++ b/tests/frame/test_frame_publishing.py @@ -145,6 +145,6 @@ def test_pub_cam_thread(): pub_cam_thread(5) mock_thread.assert_called_once_with( - target=fpub.pub_cam_loop, args=(5, (-1, -1), True, 240) + target=fpub.pub_cam_loop, args=(5, (-1, -1), True, float("inf")) ) thread_instance.start.assert_called_once() diff --git a/tests/frame/test_frame_updater.py b/tests/frame/test_frame_updater.py index 6c3aa78..3230511 100644 --- a/tests/frame/test_frame_updater.py +++ b/tests/frame/test_frame_updater.py @@ -14,7 +14,7 @@ def test_init_defaults(): assert ud.callbacks == [] assert ud.request_size == (-1, -1) assert ud.high_speed == True - assert ud.fps_limit == 240 + assert ud.fps_limit == float("inf") def test_init(): @@ -57,7 +57,7 @@ def test_loop(): ud.loop() - mock_pubcam_thread.assert_called_once_with(0, (-1, -1), True, 240) + mock_pubcam_thread.assert_called_once_with(0, (-1, -1), True, float("inf")) mock_frame_sub.assert_called_once_with("0") handler_cmd_sub.assert_called_once_with("0") sub_cam.get.assert_has_calls([mock.call(blocking=True, timeout=1.0)] * 3) diff --git a/tests/window/test_subscriber_windows.py b/tests/window/test_subscriber_windows.py index b8c1836..cebd20d 100644 --- a/tests/window/test_subscriber_windows.py +++ b/tests/window/test_subscriber_windows.py @@ -181,7 +181,7 @@ def test_handle_mouse(): mock_win_cmd.mouse_pub.publish.assert_called_once_with(mock_mousey) -def test_update_window_frames(): +def test_update_frames(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( cv2, "imshow" @@ -191,13 +191,13 @@ def test_update_window_frames(): frame = np.ones((100, 100)) sw.FRAME_DICT["0"] = frame - sw.update_window_frames() + sw.update_frames() assert sw.frames == [frame] mock_imshow.assert_called_once_with("displayarray (press ESC to quit)", frame) -def test_update_window_frames_callback(): +def test_update_frames_callback(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( cv2, "imshow" @@ -217,7 +217,7 @@ def test_update_window_frames_callback(): sw.FRAME_DICT["0"] = frame sw.FRAME_DICT["1"] = frame - sw.update_window_frames() + sw.update_frames() assert sw.frames == [frame3, frame3] assert np.all(cb.mock_calls[0].args[0] == frame) @@ -230,7 +230,7 @@ def test_update_window_frames_callback(): ) -def test_update_window_frames_too_many_channels(): +def test_update_frames_too_many_channels(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( cv2, "imshow" @@ -242,7 +242,7 @@ def test_update_window_frames_too_many_channels(): frame = np.ones((100, 100, 100)) sw.FRAME_DICT["0"] = frame - sw.update_window_frames() + sw.update_frames() mock_print.assert_has_calls( [ @@ -263,7 +263,7 @@ def test_update_window_frames_too_many_channels(): assert sw.frames[0].shape[-1] == 3 -def test_update_window_frames_nested(): +def test_update_frames_nested(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( cv2, "imshow" @@ -273,7 +273,7 @@ def test_update_window_frames_nested(): frame = np.ones((20, 100, 100, 100)) sw.FRAME_DICT["0"] = frame - sw.update_window_frames() + sw.update_frames() assert np.all(sw.frames[0] == np.ones((20, 100, 100, 3))) assert len(sw.frames) == 1 @@ -319,7 +319,7 @@ def test_update_window_frames_nested(): assert np.all(mock_imshow.mock_calls[19].args[1] == np.ones((100, 100, 3))) -def test_update_window_frames_exception(): +def test_update_frames_exception(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( cv2, "imshow" @@ -330,14 +330,14 @@ def test_update_window_frames_exception(): sw.FRAME_DICT["0"] = frame with pytest.raises(RuntimeError) as e: - sw.update_window_frames() + sw.update_frames() assert e.value == frame def test_update(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( - sub_win.SubscriberWindows, "update_window_frames" + sub_win.SubscriberWindows, "update_frames" ) as mock_update_win_frames, mock.patch( "displayarray.window.subscriber_windows.window_commands" ) as mock_win_cmd, mock.patch.object( @@ -364,7 +364,7 @@ def test_update(): def test_update_with_array(): sub_win.SubscriberWindows.FRAME_DICT = {} with mock.patch.object(cv2, "namedWindow"), mock.patch.object( - sub_win.SubscriberWindows, "update_window_frames" + sub_win.SubscriberWindows, "update_frames" ) as mock_update_win_frames, mock.patch( "displayarray.window.subscriber_windows.window_commands" ) as mock_win_cmd, mock.patch.object( @@ -495,13 +495,13 @@ def test_display(): fup.assert_has_calls( [ - mock.call(0, fps_limit=240, request_size=(50, 50)), - mock.call(1, fps_limit=240, request_size=(50, 50)), + mock.call(0, fps_limit=float("inf"), request_size=(50, 50)), + mock.call(1, fps_limit=float("inf"), request_size=(50, 50)), ] ) assert fup_inst.start.call_count == 2 sws.assert_called_once_with( - window_names=["window 0", "window 1"], video_sources=(0, 1) + window_names=["window 0", "window 1"], video_sources=(0, 1), silent=False ) assert sws_inst.close_threads == [fup_inst, fup_inst] assert d == sws_inst @@ -521,7 +521,7 @@ def test_display_blocking(): assert fup_inst.start.call_count == 2 sws.assert_called_once_with( - window_names=["window 0", "window 1"], video_sources=(0, 1) + window_names=["window 0", "window 1"], video_sources=(0, 1), silent=False ) sws_inst.loop.assert_called_once() assert fup_inst.join.call_count == 2 @@ -540,8 +540,12 @@ def test_display_callbacks(): fup.assert_has_calls( [ - mock.call(0, callbacks=[cb], fps_limit=240, request_size=(-1, -1)), - mock.call(1, callbacks=[cb], fps_limit=240, request_size=(-1, -1)), + mock.call( + 0, callbacks=[cb], fps_limit=float("inf"), request_size=(-1, -1) + ), + mock.call( + 1, callbacks=[cb], fps_limit=float("inf"), request_size=(-1, -1) + ), ] ) @@ -574,10 +578,17 @@ def test_display_callbacks_dict(): fup.assert_has_calls( [ - mock.call(0, callbacks=[cb1], fps_limit=240, request_size=(-1, -1)), mock.call( - 1, callbacks=[cb1, cb2], fps_limit=240, request_size=(-1, -1) + 0, callbacks=[cb1], fps_limit=float("inf"), request_size=(-1, -1) + ), + mock.call( + 1, + callbacks=[cb1, cb2], + fps_limit=float("inf"), + request_size=(-1, -1), + ), + mock.call( + 2, callbacks=[cb3], fps_limit=float("inf"), request_size=(-1, -1) ), - mock.call(2, callbacks=[cb3], fps_limit=240, request_size=(-1, -1)), ] )