Source code for autopilot.gui.plots.video

from collections import deque
from threading import Event, Thread
from time import time, sleep

import numpy as np
import pyqtgraph as pg
from PySide2 import QtWidgets, QtCore

from autopilot import prefs


[docs]class Video(QtWidgets.QWidget): def __init__(self, videos, fps=None): """ Display Video data as it is collected. Uses the :class:`ImageItem_TimedUpdate` class to do timed frame updates. Args: videos (list, tuple): Names of video streams that will be displayed fps (int): if None, draw according to ``prefs.get('DRAWFPS')``. Otherwise frequency of widget update Attributes: videos (list, tuple): Names of video streams that will be displayed fps (int): if None, draw according to ``prefs.get('DRAWFPS')``. Otherwise frequency of widget update ifps (int): 1/fps, duration of frame in s qs (dict): Dictionary of :class:`~queue.Queue`s in which frames will be dumped quitting (:class:`threading.Event`): Signal to quit drawing update_thread (:class:`threading.Thread`): Thread with target=:meth:`~.Video._update_frame` layout (:class:`PySide2.QtWidgets.QGridLayout`): Widget layout vid_widgets (dict): dict containing widgets for each of the individual video streams. """ super(Video, self).__init__() self.videos = videos if fps is None: if prefs.get( 'DRAWFPS'): self.fps = prefs.get('DRAWFPS') else: self.fps = 10 else: self.fps = fps self.ifps = 1.0/self.fps self.layout = None self.vid_widgets = {} #self.q = Queue(maxsize=1) self.qs = {} self.quitting = Event() self.quitting.clear() self.init_gui() self.update_thread = Thread(target=self._update_frame) self.update_thread.setDaemon(True) self.update_thread.start()
[docs] def init_gui(self): self.layout = QtWidgets.QGridLayout() self.vid_widgets = {} for i, vid in enumerate(self.videos): vid_label = QtWidgets.QLabel(vid) # https://github.com/pyqtgraph/pyqtgraph/blob/3d3d0a24590a59097b6906d34b7a43d54305368d/examples/VideoSpeedTest.py#L51 graphicsView= pg.GraphicsView(self) vb = pg.ViewBox() graphicsView.setCentralItem(vb) vb.setAspectLocked() #img = pg.ImageItem() img = ImageItem_TimedUpdate() vb.addItem(img) self.vid_widgets[vid] = (graphicsView, vb, img) # 3 videos in a row row = np.floor(i/3.)*2 col = i%3 self.layout.addWidget(vid_label, row,col, 1,1) self.layout.addWidget(self.vid_widgets[vid][0],row+1,col,5,1) # make queue for vid self.qs[vid] = deque(maxlen=1) self.setLayout(self.layout) self.resize(600,700) self.show()
def _update_frame(self): """ Pulls frames from :attr:`.Video.qs` and feeds them to the video widgets. Internal method, run in thread. """ last_time = 0 this_time = 0 while not self.quitting.is_set(): for vid, q in self.qs.items(): data = None try: data = q.popleft() self.vid_widgets[vid][2].setImage(data) except IndexError: pass except KeyError: pass this_time = time() sleep(max(self.ifps-(this_time-last_time), 0)) last_time = this_time
[docs] def update_frame(self, video, data): """ Put a frame for a video stream into its queue. If there is a waiting frame, pull it from the queue first -- it's old now. Args: video (str): name of video stream data (:class:`numpy.ndarray`): video frame """ #pdb.set_trace() # cur_time = time() try: # put the new frame in there. self.qs[video].append(data) except KeyError: return
[docs] def release(self): self.quitting.set()
VIDEO_TIMER = None
[docs]class ImageItem_TimedUpdate(pg.ImageItem): """ Reclass of :class:`pyqtgraph.ImageItem` to update with a fixed fps. Rather than calling :meth:`~pyqtgraph.ImageItem.update` every time a frame is updated, call it according to the timer. fps is set according to ``prefs.get('DRAWFPS')``, if not available, draw at 10fps Attributes: timer (:class:`~PySide2.QtCore.QTimer`): Timer held in ``globals()`` that synchronizes frame updates across image items """ def __init__(self, *args, **kwargs): super(ImageItem_TimedUpdate, self).__init__(*args, **kwargs) if globals()['VIDEO_TIMER'] is None: globals()['VIDEO_TIMER'] = QtCore.QTimer() self.timer = globals()['VIDEO_TIMER'] self.timer.stop() self.timer.timeout.connect(self.update_img) if prefs.get( 'DRAWFPS'): self.fps = prefs.get('DRAWFPS') else: self.fps = 10. self.timer.start(1./self.fps)
[docs] def setImage(self, image=None, autoLevels=None, **kargs): #profile = debug.Profiler() gotNewData = False if image is None: if self.image is None: return else: gotNewData = True shapeChanged = (self.image is None or image.shape != self.image.shape) image = image.view(np.ndarray) if self.image is None or image.dtype != self.image.dtype: self._effectiveLut = None self.image = image if self.image.shape[0] > 2 ** 15 - 1 or self.image.shape[1] > 2 ** 15 - 1: if 'autoDownsample' not in kargs: kargs['autoDownsample'] = True if shapeChanged: self.prepareGeometryChange() self.informViewBoundsChanged() #profile() if autoLevels is None: if 'levels' in kargs: autoLevels = False else: autoLevels = True if autoLevels: img = self.image while img.size > 2 ** 16: img = img[::2, ::2] mn, mx = np.nanmin(img), np.nanmax(img) # mn and mx can still be NaN if the data is all-NaN if mn == mx or np.isnan(mn) or np.isnan(mx): mn = 0 mx = 255 kargs['levels'] = [mn, mx] self.setOpts(update=False, **kargs) self.qimage = None if gotNewData: self.sigImageChanged.emit()
[docs] def update_img(self): """ Call :meth:`~ImageItem_TimedUpdate.update` """ self.update()
def __del__(self): super(ImageItem_TimedUpdate,self).__del__() self.timer.stop()
# # class VideoCV(mp.Process): # def __init__(self, videos, fps=30, parent=None): # super(VideoCV, self).__init__() # self.videos = videos # # self.last_update = 0 # self.fps = fps # self.ifps = 1.0/fps # # # #self.q = Queue(maxsize=1) # self.qs = {} # for vid in self.videos: # self.qs[vid] = mp.Queue(maxsize=1) # # self.positions = {} # n_rows = 0 # n_cols = 0 # for i, vid in enumerate(sorted(self.videos)): # # 3 videos to a row # row = np.floor(i/3.)*2 # col = i%3 # if row>n_rows: # n_rows = row # if col>n_cols: # n_cols = col # self.positions[vid] = (row, col) # # self.n_rows = n_rows+1 # self.n_cols = n_cols+1 # # # # # computed as we receive images # self.sizes = {} # self.resize_factors = {} # # self.quitting = mp.Event() # self.quitting.clear() # # def run(self): # # win = cv2.namedWindow('vid', cv2.WINDOW_NORMAL) # max_width = 0 # max_height = 0 # # img_array = None # while not self.quitting.is_set(): # pdb.set_trace() # for vid, q in self.qs.items(): # try: # data = q.get_nowait() # except Empty: # continue # # if vid not in self.sizes.keys(): # self.sizes[vid] = (data.shape[0], data.shape[1]) # max_width, max_height = self.calc_resize() # img_array = np.zeros((max_height*self.n_rows, max_width*self.n_cols)) # # data = cv2.resize(data, self.resize_factors[vid]) # top = self.positions[vid][0] * max_height # left = self.positions[vid][1] * max_width # img_array[top:top+data.shape[0], left:left+data.shape[1]] = data # # cv2.imshow('vid', img_array) # cv2.waitKey(0) # # # # def calc_resize(self): # max_width = 0 # max_height = 0 # for vid, size in self.sizes: # if size[1]>max_width: # # set both so we don't split # max_width = size[1] # max_height = size[0] # elif size[0]>max_height: # max_width = size[1] # max_height=size[0] # # for vid, size in self.sizes: # self.resize_factors[vid] = (float(max_width)/size[0], float(max_height)/size[1]) # # return max_width, max_height # # # def update_frame(self, video, data): # #pdb.set_trace() # # cur_time = time() # # try: # # if there's a waiting frame, it's old now so pull it. # _ = self.qs[video].get_nowait() # except Empty: # pass # # try: # # put the new frame in there. # self.qs[video].put_nowait(data) # except Full: # return # except KeyError: # return # # if (cur_time-self.last_update)>self.ifps: # # try: # # self.vid_widgets[video].setImage(data) # # #self.vid_widgets[video].update() # # except KeyError: # # return # # self.last_update = cur_time # #self.update() # #self.app.processEvents() # # def close(self): # self.quitting.set()