Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/luxonis/depthai
Browse files Browse the repository at this point in the history
  • Loading branch information
MaticTonin committed Aug 4, 2023
2 parents 4a1f482 + 11f12f0 commit 5850728
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 45 deletions.
86 changes: 86 additions & 0 deletions launcher/choose_app_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from PyQt5 import QtCore, QtWidgets
import os
from pathlib import Path


class ImageButton(QtWidgets.QPushButton):
def __init__(self, icon_path, parent=None):
super().__init__(parent)
icon_path = icon_path.replace('\\', '/')
self.setStyleSheet(f"""
QPushButton {{
border: none;
border-image: url({icon_path}) 0 0 0 0 stretch stretch;
}}
QPushButton:hover {{
border-image: url({icon_path}) 0 0 0 0 stretch stretch;
border: 3px solid #999999;
}}
""")


class CardWidget(QtWidgets.QWidget):
clicked = QtCore.pyqtSignal()

def __init__(self, title, image_path: Path, parent=None):
super(CardWidget, self).__init__(parent)

# Create an image button with the given card image
path = os.path.normpath(image_path)
button = ImageButton(path, self)

# Make the button fill all available space
button.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)

# Add the widget to the UI
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(button)

# Connect button to signal
button.clicked.connect(self.clicked)

def resizeEvent(self, event):
# Resize the widget to keep the aspect ratio of the image
width = self.width()
new_height = width * 64 // 177 # Adjust height based on width
self.resize(width, int(new_height))


class ChooseAppDialog(QtWidgets.QDialog):
viewerChosen: bool = False

def __init__(self, parent: QtWidgets.QWidget=None):
super(ChooseAppDialog, self).__init__(parent)
self.setWindowTitle("Choose an application")

hbox = QtWidgets.QHBoxLayout()
hbox.setContentsMargins(0, 0, 0, 0)
file_path = Path(os.path.abspath(os.path.dirname(__file__)))
demo_image_path = file_path / "demo_card.png"
viewer_image_path = file_path / "viewer_card.png"
demo_card = CardWidget("DepthAI Demo", demo_image_path)
viewer_card = CardWidget("DepthAI Viewer", viewer_image_path)
hbox.addWidget(demo_card)
hbox.addWidget(viewer_card)
self.setLayout(hbox)

demo_card.clicked.connect(self.runDemo)
viewer_card.clicked.connect(self.runViewer)

# Get screen dimensions
screen = QtWidgets.QApplication.instance().primaryScreen()
screen_size = screen.size()
width = screen_size.width() // 2
height = width // 2 * 64 // 177
self.resize(width, height)

@QtCore.pyqtSlot()
def runDemo(self):
self.accept()

@QtCore.pyqtSlot()
def runViewer(self):
self.viewerChosen = True
self.accept()
Binary file added launcher/demo_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 115 additions & 44 deletions launcher/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets

from choose_app_dialog import ChooseAppDialog

# Constants
SCRIPT_DIRECTORY=Path(os.path.abspath(os.path.dirname(__file__)))
DEPTHAI_DEMO_SCRIPT='depthai_demo.py'
Expand Down Expand Up @@ -69,11 +71,11 @@ def flush(self):
# Create splash screen
splashScreen = SplashScreen(str(SCRIPT_DIRECTORY/'splash2.png'))

def closeSplash():
splashScreen.hide()

class Worker(QtCore.QThread):
signalUpdateQuestion = QtCore.pyqtSignal(str, str)
signalChooseApp = QtCore.pyqtSignal()
signalCloseSplash = QtCore.pyqtSignal()
sigInfo = QtCore.pyqtSignal(str, str)
sigCritical = QtCore.pyqtSignal(str, str)
sigWarning = QtCore.pyqtSignal(str, str)
Expand All @@ -89,6 +91,22 @@ def updateQuestion(self, title, message):
else:
self.shouldUpdate = False
return False

@QtCore.pyqtSlot()
def chooseApp(self) -> None:
"""
Until Depthai Viewer is in beta, allow the user to choose between running the demo or the viewer.
"""
# If the dialog is rejected, the user has clicked exit - so we exit
dialog = ChooseAppDialog(splashScreen)
if dialog.exec_() == QtWidgets.QDialog.Accepted:
self.viewerChosen = dialog.viewerChosen
else:
raise RuntimeError("User cancelled app choice dialog")

@QtCore.pyqtSlot()
def closeSplash(self):
splashScreen.close()

@QtCore.pyqtSlot(str,str)
def showInformation(self, title, message):
Expand All @@ -105,6 +123,8 @@ def showCritical(self, title, message):
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.signalUpdateQuestion[str, str].connect(self.updateQuestion, QtCore.Qt.BlockingQueuedConnection)
self.signalChooseApp.connect(self.chooseApp, QtCore.Qt.BlockingQueuedConnection)
self.signalCloseSplash.connect(self.closeSplash, QtCore.Qt.BlockingQueuedConnection)
self.sigInfo[str, str].connect(self.showInformation, QtCore.Qt.BlockingQueuedConnection)
self.sigCritical[str, str].connect(self.showCritical, QtCore.Qt.BlockingQueuedConnection)
self.sigWarning[str, str].connect(self.showWarning, QtCore.Qt.BlockingQueuedConnection)
Expand Down Expand Up @@ -306,58 +326,110 @@ def run(self):
self.sigWarning.emit(title, message)

try:
self.signalChooseApp.emit()
# Set to quit splash screen a little after subprocess is ran
skipSplashQuitFirstTime = False
def removeSplash():
time.sleep(2.5)
if not skipSplashQuitFirstTime:
closeSplash()
self.signalCloseSplash.emit()
quitThread = threading.Thread(target=removeSplash)
quitThread.start()
if self.viewerChosen:
print("Depthai Viewer chosen, checking if depthai-viewer is installed.")
# Check if depthai-viewer is installed
is_viewer_installed_cmd = [sys.executable, "-m", "pip", "show", "depthai-viewer"]
viewer_available_ret = subprocess.run(is_viewer_installed_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if viewer_available_ret.returncode != 0:
splashScreen.updateSplashMessage('Installing Depthai Viewer ...')
splashScreen.enableHeartbeat(True)
print("Depthai Viewer not installed, installing...")
# Depthai Viewer isn't installed, install it
# First upgrade pip
subprocess.run([sys.executable, "-m", "pip", "install", "-U", "pip"], check=True)
# Install depthai-viewer - Don't check, it can error out because of dependency conflicts but still install successfully
subprocess.run([sys.executable, "-m", "pip", "install", "depthai-viewer"])
# Check again if depthai-viewer is installed
viewer_available_ret = subprocess.run(is_viewer_installed_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if viewer_available_ret.returncode != 0:
raise RuntimeError("Depthai Viewer failed to install.")
splashScreen.updateSplashMessage('')
splashScreen.enableHeartbeat(False)

# All ready, run the depthai_demo.py as a separate process
ret = subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_DEMO_SCRIPT}'], cwd=pathToDepthaiRepository, stderr=subprocess.PIPE)

# Print out stderr first
sys.stderr.write(ret.stderr.decode())

print(f'DepthAI Demo ret code: {ret.returncode}')
# Install dependencies if demo signaled missing dependencies
if ret.returncode == 42:
skipSplashQuitFirstTime = True
print(f'Dependency issue raised. Retrying by installing requirements and restarting demo.')

# present message of installing dependencies
splashScreen.updateSplashMessage('Installing DepthAI Requirements ...')
splashScreen.enableHeartbeat(True)
viewer_version = version.parse(viewer_available_ret.stdout.decode().splitlines()[1].split(" ")[1].strip())
print(f"Installed Depthai Viewer version: {viewer_version}")
# Get latest depthai-viewer version
latest_ret = subprocess.run([sys.executable, "-m", "pip", "index", "versions", "depthai-viewer"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if latest_ret.returncode != 0:
raise RuntimeError("Couldn't get latest depthai-viewer version.")
latest_viewer_version = version.parse(latest_ret.stdout.decode().split("LATEST:")[1].strip())
print(f"Latest Depthai Viewer version: {latest_viewer_version}")
if latest_viewer_version > viewer_version:
# Update is available, ask user if they want to update
title = 'DepthAI Viewer update available'
message = f'Version {str(latest_viewer_version)} of depthai-viewer is available, current version {str(viewer_version)}. Would you like to update?'
self.signalUpdateQuestion.emit(title, message)
if self.shouldUpdate:
splashScreen.updateSplashMessage(f'Updating Depthai Viewer to version {latest_viewer_version} ...')
splashScreen.enableHeartbeat(True)
# Update depthai-viewer
subprocess.run([sys.executable, "-m", "pip", "install", "-U", "depthai-viewer"])
# Test again to see if viewer is installed and updated
viewer_available_ret = subprocess.run(is_viewer_installed_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if viewer_available_ret.returncode != 0:
raise RuntimeError(f"Installing version {latest_viewer_version} failed.")
viewer_version = version.parse(viewer_available_ret.stdout.decode().splitlines()[1].split(" ")[1].strip())
if latest_viewer_version > viewer_version:
raise RuntimeError("Depthai Viewer failed to update.")
splashScreen.updateSplashMessage('')
splashScreen.enableHeartbeat(False)

# All ready, run the depthai-viewer as a seperate process
ret = subprocess.run([sys.executable, "-m", "depthai_viewer"])
else:
# All ready, run the depthai_demo.py as a separate process
ret = subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_DEMO_SCRIPT}'], cwd=pathToDepthaiRepository, stderr=subprocess.PIPE)

# Install requirements for depthai_demo.py
MAX_RETRY_COUNT = 3
installReqCall = None
for retry in range(0, MAX_RETRY_COUNT):
installReqCall = subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT}'], cwd=pathToDepthaiRepository, stderr=subprocess.PIPE)
if installReqCall.returncode == 0:
break
if installReqCall.returncode != 0:
# Some error happened. Notify user
title = 'Error Installing DepthAI Requirements'
message = f"Couldn't install DepthAI requirements. Check internet connection and try again. Log available at: {LOG_FILE_PATH}"
print(f'Message Box ({title}): {message}')
print(f'Install dependencies call failed with return code: {installReqCall.returncode}, message: {installReqCall.stderr.decode()}')
self.sigCritical.emit(title, message)
raise Exception(title)
# Print out stderr first
sys.stderr.write(ret.stderr.decode())

# Remove message and animation
splashScreen.updateSplashMessage('')
splashScreen.enableHeartbeat(False)
print(f'DepthAI Demo ret code: {ret.returncode}')
# Install dependencies if demo signaled missing dependencies
if ret.returncode == 42:
skipSplashQuitFirstTime = True
print(f'Dependency issue raised. Retrying by installing requirements and restarting demo.')

quitThread.join()
skipSplashQuitFirstTime = False
quitThread = threading.Thread(target=removeSplash)
quitThread.start()
# present message of installing dependencies
splashScreen.updateSplashMessage('Installing DepthAI Requirements ...')
splashScreen.enableHeartbeat(True)

# All ready, run the depthai_demo.py as a separate process
subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_DEMO_SCRIPT}'], cwd=pathToDepthaiRepository)
# Install requirements for depthai_demo.py
MAX_RETRY_COUNT = 3
installReqCall = None
for retry in range(0, MAX_RETRY_COUNT):
installReqCall = subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT}'], cwd=pathToDepthaiRepository, stderr=subprocess.PIPE)
if installReqCall.returncode == 0:
break
if installReqCall.returncode != 0:
# Some error happened. Notify user
title = 'Error Installing DepthAI Requirements'
message = f"Couldn't install DepthAI requirements. Check internet connection and try again. Log available at: {LOG_FILE_PATH}"
print(f'Message Box ({title}): {message}')
print(f'Install dependencies call failed with return code: {installReqCall.returncode}, message: {installReqCall.stderr.decode()}')
self.sigCritical.emit(title, message)
raise Exception(title)

# Remove message and animation
splashScreen.updateSplashMessage('')
splashScreen.enableHeartbeat(False)

quitThread.join()
skipSplashQuitFirstTime = False
quitThread = threading.Thread(target=removeSplash)
quitThread.start()

# All ready, run the depthai_demo.py as a separate process
subprocess.run([sys.executable, f'{pathToDepthaiRepository}/{DEPTHAI_DEMO_SCRIPT}'], cwd=pathToDepthaiRepository)
except:
pass
finally:
Expand All @@ -368,8 +440,7 @@ def removeSplash():
print(f'Unknown error occured ({ex}), exiting...')
finally:
# At the end quit anyway
closeSplash()
splashScreen.close()
self.signalCloseSplash.emit()
qApp.exit()

qApp.worker = Worker()
Expand Down
Binary file added launcher/viewer_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion launcher/windows/installer_win64.iss
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ Source: "..\{#MyAppIconName}"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\launcher.py"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\requirements.txt"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\splash2.png"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\demo_card.png"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\viewer_card.png"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\splash_screen.py"; DestDir: "{app}"; Flags: ignoreversion
; Source: "..\choose_app_dialog.py"; DestDir: "{app}"; Flags: ignoreversion
; ; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Expand Down Expand Up @@ -208,4 +211,4 @@ end;
[UninstallDelete]
Type: filesandordirs; Name: "{app}"
Type: filesandordirs; Name: "{app}"

0 comments on commit 5850728

Please sign in to comment.