Skip to content

Commit 97d4385

Browse files
committed
Fixed the crash on termination of main window due to cv2 camera stream not closed properly
1 parent 57e7bb5 commit 97d4385

File tree

2 files changed

+65
-30
lines changed

2 files changed

+65
-30
lines changed

Dashboard/CameraWidget.py

+27-8
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ class Thread(QThread):
1111
changePixmap = pyqtSignal(QImage)
1212

1313
def __init__(self, parent=None, url=0):
14-
super(QThread, self).__init__(parent)
15-
self.url = url
14+
super(QThread, self).__init__(parent)
15+
self.url = url
16+
self.is_running = True
17+
self.cap = None
18+
19+
def stop(self):
20+
if self.cap is not None:
21+
self.cap.release()
22+
self.is_running = False
1623

1724
def run(self):
18-
cap = cv2.VideoCapture(self.url)
19-
while True:
20-
ret, frame = cap.read()
25+
self.cap = cv2.VideoCapture(self.url)
26+
while self.is_running:
27+
ret, frame = self.cap.read()
2128
if ret:
2229
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
2330
convertToQtFormat = QImage(rgbImage.data,
@@ -27,7 +34,15 @@ def run(self):
2734
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
2835
self.changePixmap.emit(p)
2936

37+
if self.isInterruptionRequested():
38+
self.stop()
39+
3040
class CameraWidget(QFrame):
41+
"""
42+
From the documentation: The QFrame class is the base class of widgets
43+
that can have a frame.
44+
45+
"""
3146
def __init__(self, parent=None, url='rtsp://192.168.1.64/1'):
3247
super().__init__()
3348
self.url=url
@@ -37,6 +52,10 @@ def __init__(self, parent=None, url='rtsp://192.168.1.64/1'):
3752
def setImage(self, image):
3853
self.label.setPixmap(QPixmap.fromImage(image))
3954

55+
def close(self):
56+
self.thread.requestInterruption()
57+
self.thread.wait()
58+
4059
def init_UI(self):
4160
#self.setWindowTitle(self.title)
4261
#self.setGeometry(self.left, self.top, self.width, self.height)
@@ -52,6 +71,6 @@ def init_UI(self):
5271
self.setLayout(layout)
5372

5473
# Start acquisition thread
55-
th = Thread(self, self.url)
56-
th.changePixmap.connect(self.setImage)
57-
th.start()
74+
self.thread = Thread(self, self.url)
75+
self.thread.changePixmap.connect(self.setImage)
76+
self.thread.start()

apps/launch_qt_dashboard.py

+38-22
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from PyQt5.QtWidgets import QWidget, QSizePolicy, QVBoxLayout, QMessageBox
1515
from PyQt5.QtGui import QVector3D
1616
from PyQt5.Qt3DExtras import Qt3DWindow, QOrbitCameraController
17-
from PyQt5.QtCore import Qt, QThread, pyqtSlot
17+
from PyQt5.QtCore import Qt, QThread, pyqtSignal, pyqtSlot
1818

1919
# Astropy stuff
2020
from astropy import units as u
@@ -43,12 +43,16 @@
4343

4444

4545
class MainWindow(QMainWindow):
46-
def __init__(self, view3D, camera_widget=None, dashboard_widget=None):
46+
47+
closing = pyqtSignal()
48+
49+
def __init__(self, view3D=None, camera_widget=None, dashboard_widget=None):
4750
super().__init__()
4851

4952
# Various views/widgets
5053
self.view3D = view3D
5154
self.camera_widget = camera_widget
55+
self.closing.connect(self.camera_widget.close)
5256
self.dashboard_widget = dashboard_widget
5357

5458
# Change visual parameters and setup widgets on the main window
@@ -79,39 +83,43 @@ def init_UI(self):
7983
self.helpMenu.addAction(self.creditsAct)
8084

8185
# General layout
82-
self.layout = QVBoxLayout()
83-
84-
# 3D view
85-
#self.dock_view3D = QDockWidget('Mount simulator', self)
86-
#self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_view3D)
87-
self.widget3D = QWidget.createWindowContainer(self.view3D.window, self)
88-
#self.widget3D.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
89-
#self.dock_view3D.setWidget(self.widget3D)
90-
self.setCentralWidget(self.widget3D)
91-
#self.layout.addWidget(self.widget3D)
92-
93-
# Dock safety camera on the right
86+
#self.layout = QVBoxLayout()
87+
88+
# Webbrowser for dashboard should be the main widget
89+
if self.dashboard_widget is not None:
90+
#self.dock_dashboard = QDockWidget('Main dashboard', self)
91+
#self.addDockWidget(Qt.RightDockWidgetArea, self.dock_dashboard)
92+
#self.dashboard_widget.setSizePolicy(QSizePolicy.Fixed,
93+
# QSizePolicy.Fixed)
94+
#self.dock_dashboard.setWidget(self.dashboard_widget)
95+
self.setCentralWidget(self.dashboard_widget)
96+
97+
# Dock safety camera on the Left
9498
if self.camera_widget is not None:
9599
self.dock_camera = QDockWidget('Surveillance camera', self)
96100
self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_camera)
97101
#self.camera_widget.setSizePolicy(QSizePolicy.Fixed,
98102
# QSizePolicy.Fixed)
99103
self.dock_camera.setWidget(self.camera_widget)
100104

101-
# Dock webbrowser for dashboard on the right
102-
if self.dashboard_widget is not None:
103-
self.dock_dashboard = QDockWidget('Main dashboard', self)
104-
self.addDockWidget(Qt.RightDockWidgetArea, self.dock_dashboard)
105-
#self.dashboard_widget.setSizePolicy(QSizePolicy.Fixed,
106-
# QSizePolicy.Fixed)
107-
self.dock_dashboard.setWidget(self.dashboard_widget)
105+
# 3D view set as right widget
106+
if self.view3D is not None:
107+
self.widget3D = QWidget.createWindowContainer(self.view3D.window, self)
108+
self.dock_view3D = QDockWidget('Mount simulator', self)
109+
self.addDockWidget(Qt.RightDockWidgetArea, self.dock_view3D)
110+
#self.widget3D.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
111+
self.dock_view3D.setWidget(self.widget3D)
112+
#self.setCentralWidget(self.widget3D)
113+
#self.layout.addWidget(self.widget3D)
108114

109115
# Global stuff
110-
self.setLayout(self.layout)
116+
#self.setLayout(self.layout)
111117
self.setWindowTitle('Remote observatory dashboard')
112118
self.show()
113119

114120
def closeEvent(self, event):
121+
# emit a signal for registered custom slot to perform cleanup if needed
122+
self.closing.emit()
115123
self.quit()
116124

117125
def quit(self):
@@ -173,6 +181,14 @@ def update_coord(self, coord):
173181

174182
if __name__ == "__main__":
175183

184+
def trap_exc_during_debug(*args):
185+
# when app raises uncaught exception, print info
186+
print(args)
187+
188+
# install exception hook: without this, uncaught exception would cause
189+
# application to exit
190+
sys.excepthook = trap_exc_during_debug
191+
176192
# load the logging configuration
177193
logging.config.fileConfig('logging.ini')
178194

0 commit comments

Comments
 (0)