Trouble accessing a variable from one class in another

Hi,

This is beginner’s question on using a variable between different classes. I’m building an application that involves counting objects on video frames from a usb camera. I use OpenCV’s simple blob detector. I used code from here to embed simple blob detector in a PyQT-based GUI-application.

I manage to print the number of counted objects on each video frame using print(len(keypoints)) added in the while loop within the run function (see code below).

However, I do not manage to access the value of ‘keypoints’ outside the VideoThread class (e.g., to read out the value and display it in a QPlainTextEdit box in the GUI when the mouse is clicked).
In the below example, I try to read out the length of keypoints on a mousePressEvent in the Tab class of the GUI.

When using: print(len(keypoints)) in the mousePressEvent function in the Tab class, I get: NameError: name 'keypoints' is not defined

When using print(len(VideoThread.keypoints)), I get: AttributeError: type object 'VideoThread' has no attribute 'keypoints'

When I declare keypoints in the VideoThread class as a list (keypoints = []), I get no errors but len(VideoThread.keypoints) returns 0, because keypoints remains an empty list.

I experimented with declaring keypoins as a global variable within the run function (based on info from here), but I get Global variable 'keypoints' is undefined at the module level (in Pycharm). When I define it at the module level, I find again that it is not updated in the run method (remains 0).

How do I access the keypoints variable from outside the VideoThread class?

The code:

videothread.py for the thread that reads the camera frames:

    import cv2
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread, QStringListModel
    import numpy as np

    class VideoThread(QThread):
        change_pixmap_signal = pyqtSignal(np.ndarray)
        def __init__(self):
            super().__init__()
            self._run_flag = True

        def run(self):
            cap = cv2.VideoCapture(0)    
            while self._run_flag:
                ret, cv_img = cap.read()
                if ret:
                    detector = cv2.SimpleBlobDetector_create(params)
                    keypoints = detector.detect(cv_img)
                    cv_img = cv2.drawKeypoints(cv_img, keypoints, np.array([]), (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) 
                    self.change_pixmap_signal.emit(cv_img)
            cap.release()

        def stop(self):
            self._run_flag = False
            self.wait()

view.py for the user interface

from videothread import VideoThread
from threading import Thread
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QWidget, QAction, QTabWidget, QVBoxLayout


class Tab(QWidget):
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)
        
        # Instantiate 'tabs' as an object from the QTabWidget() class
        self.tabs = QTabWidget()
        # All tabs are instantiated as seperate QWidget() objects
        self.tab1 = QWidget()
        self.tab2 = QWidget()
        # Add tab1 and tab2 to QTabWidget 'tabs'
        self.tabs.addTab(self.tab1, "Beeldacquisitie")
        self.tabs.addTab(self.tab2, "Resultaten")

    def mousePressEvent(self, event):
	# self.output is a QPlainTextEdit box
        self.output.setPlainText(str(len(VideoThread.keypoints)))

So, you want to run the run() function from VideoThread class, which calculates keypoints, and then you want that value to be accessible in the Tab class for printing?

If that is so, there are a LOT of problems in your code. Let me point out them for you.

  • You have to actually run the run() function to access the keypoints value.
  • The scope of keypoints variable in your code is limited to the run() function only. Means it is local. So you can’t access it from outside that function.
  • To access a variable from any class, you have to use an object of the class, not the class itself. Look what you did in the last line of Tab function.

Fixed code: (I guess)

videothread.py for the thread that reads the camera frames:

import cv2
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread, QStringListModel
import numpy as np

class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)
    def __init__(self):
        super().__init__()
        self._run_flag = True

    def run(self):
    cap = cv2.VideoCapture(0)    
    while self._run_flag:
        ret, cv_img = cap.read()
        if ret:
            detector = cv2.SimpleBlobDetector_create(params)
            self.keypoints = detector.detect(cv_img)
            cv_img = cv2.drawKeypoints(cv_img, self.keypoints, np.array([]), (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) 
            self.change_pixmap_signal.emit(cv_img)
    cap.release()

def stop(self):
    self._run_flag = False
    self.wait()

view.py for the user interface

from videothread import VideoThread
from threading import Thread
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QWidget, QAction, QTabWidget, QVBoxLayout


class Tab(QWidget):
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)
        
        # Instantiate 'tabs' as an object from the QTabWidget() class
        self.tabs = QTabWidget()
        # All tabs are instantiated as seperate QWidget() objects
        self.tab1 = QWidget()
        self.tab2 = QWidget()
        # Add tab1 and tab2 to QTabWidget 'tabs'
        self.tabs.addTab(self.tab1, "Beeldacquisitie")
        self.tabs.addTab(self.tab2, "Resultaten")

    def mousePressEvent(self, event):
	    # self.output is a QPlainTextEdit box
        videoThreadObj = VideoThread()
        videoThreadObj.run()
        self.output.setPlainText(str(len(videoThreadObj.keypoints)))

Thank you.

1 Like

Hi. Thanks for your reply. I tried what you suggested but I get an attributeError (videoThreadObj does not have a keypoints attribute).
However, I found a different solution; in the pyqtSignal change_pixmap_signal I included keypoints as an argument:

…
change_pixmap_signal = pyqtSignal(np.ndarray, list)
…
self.change_pixmap_signal.emit(cv_img, keypoints)

At each frame the signal is emitted with a cv_img variable that is used to display the camera frame in a QLabel in the Tab class and with keypoints that I copy into another variable that can be accessed within an mousePressEvent in the Tab class.

Many thanks for thinking along!