2222
2323import numpy as np
2424
25- from qtutils import UiLoader , inmain_later
25+ from qtutils import UiLoader , inmain_decorator
2626import qtutils .icons
2727from qtutils .qt import QtWidgets , QtGui , QtCore
2828import pyqtgraph as pg
@@ -58,7 +58,13 @@ def __init__(self, image_view, label_fps):
5858 self .frame_rate = None
5959 self .update_event = None
6060
61+ @inmain_decorator (wait_for_return = True )
6162 def handler (self , data ):
63+ # Acknowledge immediately so that the worker process can begin acquiring the
64+ # next frame. This increases the possible frame rate since we may render a frame
65+ # whilst acquiring the next, but does not allow us to accumulate a backlog since
66+ # only one call to this method may occur at a time.
67+ self .send ([b'ok' ])
6268 md = json .loads (data [0 ])
6369 image = np .frombuffer (memoryview (data [1 ]), dtype = md ['dtype' ])
6470 image = image .reshape (md ['shape' ])
@@ -71,25 +77,6 @@ def handler(self, data):
7177 else :
7278 self .frame_rate = 1 / dt
7379 self .last_frame_time = this_frame_time
74- # Wait for the previous update to compete so we don't accumulate a backlog:
75- if self .update_event is not None :
76- while True :
77- # Don't block, and check for self.stopping regularly in case we are
78- # shutting down. Otherwise if shutdown is called from the main thread we
79- # would deadlock.
80- try :
81- self .update_event .get (timeout = 0.1 )
82- break
83- except Empty :
84- if self .stopping :
85- return
86- self .update_event = inmain_later (self .update , image , self .frame_rate )
87- return [b'ok' ]
88-
89- def update (self , image , frame_rate ):
90- if not self .mainloop_thread .is_alive ():
91- # We have been shut down. Nothing to do here.
92- return
9380 if self .image_view .image is None :
9481 # First time setting an image. Do autoscaling etc:
9582 self .image_view .setImage (image .swapaxes (- 1 , - 2 ))
@@ -99,8 +86,21 @@ def update(self, image, frame_rate):
9986 image .swapaxes (- 1 , - 2 ), autoRange = False , autoLevels = False
10087 )
10188 # Update fps indicator:
102- if frame_rate is not None :
103- self .label_fps .setText (f"{ frame_rate :.01f} fps" )
89+ if self .frame_rate is not None :
90+ self .label_fps .setText (f"{ self .frame_rate :.01f} fps" )
91+
92+ # Tell Qt to send posted events immediately to prevent a backlog of paint events
93+ # and other low-priority events. It seems that we cannot make our qtutils
94+ # CallEvents (which are used to call this method in the main thread) low enough
95+ # priority to ensure all other occur before our next call to self.handler()
96+ # runs. This may be because the CallEvents used by qtutils.invoke_in_main have
97+ # their own event handler (qtutils.invoke_in_main.Caller), perhaps posted event
98+ # priorities are only meaningful within the context of a single event handler,
99+ # and not for the Qt event loop as a whole. In any case, this seems to fix it.
100+ # Manually calling this is usually a sign of bad coding, but I think it is the
101+ # right solution to this problem. This solves issue #36.
102+ QtGui .QApplication .instance ().sendPostedEvents ()
103+ return self .NO_RESPONSE
104104
105105
106106class IMAQdxCameraTab (DeviceTab ):
@@ -286,4 +286,4 @@ def restart(self, *args, **kwargs):
286286 # Must manually stop the receiving server upon tab restart, otherwise it does
287287 # not get cleaned up:
288288 self .image_receiver .shutdown ()
289- return DeviceTab .restart (self , * args , ** kwargs )
289+ return DeviceTab .restart (self , * args , ** kwargs )
0 commit comments