0

I am currently working on developing a utility for visualizing data collection. I have created a PyQt5 GUI, and I am using the vtk library to visualize live data from an external source. My current organizational structure is as follows:

  1. initialize the GUI and all widgets, layouts, etc. The central widget of this GUI is a QVTKRenderWindowInteractor. I then add all of the necessary VTK actors, mappers, etc. to the renderer.

  2. After the GUI has been initialized, I create an instance of a QThread called ContinuousDataThread, connect it to the slot function in the GUI, and start the thread. This thread is responsible for continuously pulling data from an external source, and updating the main GUI with processed data via a signal/slot relationship.

  3. As the data is sent back to the GUI, the VTK render window is updated via the slot function to reflect the live data.

This approach works perfectly for the vast majority of the time that the GUI is being used. However, in certain instances, interacting with other GUI elements seems to break the connection between the thread and the GUI. This leads to a laggy VTK render window and live data is no longer being continuously transmitted. The GUI does not freeze up or quit, and no errors are thrown anywhere in the process. This issue is not repeatable for any singular GUI element interaction, but rather seemingly random. I believe this may be due to a timing/synchronization issue, but I am not quite sure how to solve this. Below is the code for the thread:

class ContinuousDataThread(QThread):
    angle_signal = pyqtSignal(list)

    def __init__(self, neok, kdl_packet):
        super().__init__()
        self.nc = neok
        self.tracker_kdl = kdl_packet[0]
        self.schunk_kdl = kdl_packet[1]
        self.data_packet = None

    def run(self):
        # pull data continuously from source
        while True:
            data = self.nc.data
            pt_angles, ga_angles = self.get_angles(data)
            pt_coords, ga_coords = self.calculate_new_joint_locations(pt_angles, ga_angles)
            self.data_packet = [pt_coords, ga_coords]
            self.report_angles()
            time.sleep(0.025)

    def get_angles(self, task):
        api_data = task

        while api_data is None:
            api_data = task
        pt_angles = []
        ga_angles = []

        for i in getattr(api_data, 'tracker_angles'):
            pt_angles.append(i)
        for i in getattr(api_data, 'guidearm_angles'):
            ga_angles.append(i)

        return pt_angles, ga_angles

    def calculate_new_joint_locations(self, pt_angles, ga_angles):
        pt_matrices = Kinematics.FW_Kinematics_Matrices(self.tracker_kdl, pt_angles)
        ga_matrices = Kinematics.FW_Kinematics_Matrices(self.schunk_kdl, ga_angles)

        pt_joint_coordinates = []
        ga_joint_coordinates = []

        # update the coordinates of each pt joint
        for matrix in pt_matrices:
            pt_joint_coordinates.append(matrix[0:3, 3])

        # update the coordinates of each ga joint
        for matrix in ga_matrices:
            ga_joint_coordinates.append(matrix[0:3, 3])

        return pt_joint_coordinates, ga_joint_coordinates

    def report_angles(self):
        self.angle_signal.emit(self.data_packet)

I have tried offloading the majority of the data processing to the QThread to prevent delays in the main GUI that could lead to another signal being sent before processing is done. I have also increased the time.sleep() time inside the run() function of the QThread, and although this seems to decrease the likliness of this issue, it does not prevent it entirely, and also leads to a slower refresh rate on the VTK render window. I have also tried to keep track of the status of the ContinuousDataThread, but even after experiencing the lack of communication and VTK window glitches, .isrunning() still reports True.

Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
JCrypp
  • 1
  • what is the CPU usage normally and when the GUI is lagging ? and are there any computations being done in the app ? i suspect python threads are not able to keep the GUI updating as fast as needed due to the GIL, so if you have a single core pinned at 100% then that's the issue, in which case you have to reduce computation time or use an extension module to drop the GIL during computations. – Ahmed AEK Aug 11 '23 at 14:31
  • When you connect the angle_signal to a slot, what kind of connection are you using? Are you sure you're using QueuedConnection? Note that time.sleep does not work well with Qt5's classes. You should use self.thread.msleep() inside ContinuousDataThread, instead of time.sleep. – Carl HR Aug 11 '23 at 14:38
  • If what @AhmedAEK said is true, you could solve the issue using another process which executes in parallel. The communication could use sockets in localhost (works offline) and is fast (or any other IPC method). Though you would need more work to set things up. – Carl HR Aug 11 '23 at 14:42
  • Unfortunately, it's almost impossible to answer questions about "sometimes xyz happens". At least, please try to provide a more comprehensive [mre] (the thread code alone not sufficient, as the problem may be elsewhere), otherwise you will only get wild guesses, and that's not the purpose of StackOverflow. – musicamante Aug 11 '23 at 20:09
  • @Carl HR, I am unfamiliar with what exactly a QueuedConnection is? Can you explain further how this could help? Also , I did check the CPU usage and i'm not experiencing any unusual values while the error is occuring. As I mentioned in the original post, I cannot replicate this issue consistently. It seems to be a product of a signal triggering a slot at the same time a GUI element is being interacted with (button press, drop down change, etc) – JCrypp Aug 11 '23 at 20:16
  • When you connect a signal in qt5 there's a second parameter that is normally omitted, it's value is generally set to AutoConnection, it's [explained here in the docs](https://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads). It normally defaults to a DirectConnection, but when used on signals from another thread, it should default to QueuedConnection. It changes the way signals are emitted/processed by each listener, just read the docs about it. As you're subclassing a QThread class and declaring a signal there, I don't know what kind of connection type you're using. – Carl HR Aug 11 '23 at 23:55
  • You could force a new type of connection by setting the 2nd parameter everytime you connect a new listener to your signal: `yourThread.auto_signal.connect(listener, Qt.QueuedConnection)` (note that there many polymorphic ways to call QObject.connect). This way you're ensuring to use a QueuedConnection. As @musicamante said, as there's no [MRE](https://stackoverflow.com/help/minimal-reproducible-example), this is a wild guess, so it might not be the cause of the problem. – Carl HR Aug 12 '23 at 00:06

0 Answers0