diff --git a/.gitignore b/.gitignore index 4368b1570..cd18bf4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ build/ package/*.tar package/*.tar.gz develop-eggs/ -dist/ downloads/ eggs/ .eggs/ diff --git a/depthai_demo.py b/depthai_demo.py index edcfd7afd..a9c0272d5 100755 --- a/depthai_demo.py +++ b/depthai_demo.py @@ -1,14 +1,10 @@ #!/usr/bin/env python3 import sys +import time if sys.version_info[0] < 3: raise Exception("Must be using Python 3") -import argparse -import json import os -import time -import traceback -from functools import cmp_to_key from itertools import cycle from pathlib import Path import platform @@ -42,7 +38,7 @@ from log_system_information import make_sys_report from depthai_helpers.supervisor import Supervisor from depthai_helpers.arg_manager import parseArgs -from depthai_helpers.config_manager import ConfigManager, DEPTHAI_ZOO, DEPTHAI_VIDEOS +from depthai_helpers.config_manager import ConfigManager, DEPTHAI_ZOO, DEPTHAI_VIDEOS, prepareConfManager from depthai_helpers.metrics import MetricManager from depthai_helpers.version_check import checkRequirementsVersion from depthai_sdk import FPSHandler, loadModule, getDeviceInfo, downloadYTVideo, Previews, createBlankFrame @@ -120,9 +116,10 @@ def run_all(self, conf): self.setup(conf) self.run() - def __init__(self, displayFrames=True, onNewFrame = noop, onShowFrame = noop, onNn = noop, onReport = noop, onSetup = noop, onTeardown = noop, onIter = noop, onAppSetup = noop, onAppStart = noop, shouldRun = lambda: True, showDownloadProgress=None, collectMetrics=False): + def __init__(self, displayFrames=True, consumeFrames=True, onNewFrame = noop, onShowFrame = noop, onNn = noop, onReport = noop, onPipeline = noop, onSetup = noop, onTeardown = noop, onIter = noop, onAppSetup = noop, onAppStart = noop, shouldRun = lambda: True, showDownloadProgress=None, collectMetrics=False): self._openvinoVersion = None self._displayFrames = displayFrames + self._consumeFrames = consumeFrames self.toggleMetrics(collectMetrics) self.onNewFrame = onNewFrame @@ -130,6 +127,7 @@ def __init__(self, displayFrames=True, onNewFrame = noop, onShowFrame = noop, on self.onNn = onNn self.onReport = onReport self.onSetup = onSetup + self.onPipeline = onPipeline self.onTeardown = onTeardown self.onIter = onIter self.shouldRun = shouldRun @@ -137,7 +135,7 @@ def __init__(self, displayFrames=True, onNewFrame = noop, onShowFrame = noop, on self.onAppSetup = onAppSetup self.onAppStart = onAppStart - def setCallbacks(self, onNewFrame=None, onShowFrame=None, onNn=None, onReport=None, onSetup=None, onTeardown=None, onIter=None, onAppSetup=None, onAppStart=None, shouldRun=None, showDownloadProgress=None): + def setCallbacks(self, onNewFrame=None, onShowFrame=None, onNn=None, onReport=None, onPipeline=None, onSetup=None, onTeardown=None, onIter=None, onAppSetup=None, onAppStart=None, shouldRun=None, showDownloadProgress=None): if onNewFrame is not None: self.onNewFrame = onNewFrame if onShowFrame is not None: @@ -146,6 +144,8 @@ def setCallbacks(self, onNewFrame=None, onShowFrame=None, onNn=None, onReport=No self.onNn = onNn if onReport is not None: self.onReport = onReport + if onPipeline is not None: + self.onPipeline = onPipeline if onSetup is not None: self.onSetup = onSetup if onTeardown is not None: @@ -227,16 +227,16 @@ def setup(self, conf: ConfigManager): if self._conf.leftCameraEnabled: self._pm.createLeftCam(self._monoRes, self._conf.args.monoFps, orientation=self._conf.args.cameraOrientation.get(Previews.left.name), - xout=Previews.left.name in self._conf.args.show) + xout=Previews.left.name in self._conf.args.show and self._consumeFrames) if self._conf.rightCameraEnabled: self._pm.createRightCam(self._monoRes, self._conf.args.monoFps, orientation=self._conf.args.cameraOrientation.get(Previews.right.name), - xout=Previews.right.name in self._conf.args.show) + xout=Previews.right.name in self._conf.args.show and self._consumeFrames) if self._conf.rgbCameraEnabled: self._pm.createColorCam(previewSize=self._conf.previewSize, res=self._rgbRes, fps=self._conf.args.rgbFps, orientation=self._conf.args.cameraOrientation.get(Previews.color.name), fullFov=not self._conf.args.disableFullFovNn, - xout=Previews.color.name in self._conf.args.show) + xout=Previews.color.name in self._conf.args.show and self._consumeFrames) if self._conf.useDepth: self._pm.createDepth( @@ -247,10 +247,10 @@ def setup(self, conf: ConfigManager): self._conf.args.lrcThreshold, self._conf.args.extendedDisparity, self._conf.args.subpixel, - useDepth=Previews.depth.name in self._conf.args.show or Previews.depthRaw.name in self._conf.args.show, - useDisparity=Previews.disparity.name in self._conf.args.show or Previews.disparityColor.name in self._conf.args.show, - useRectifiedLeft=Previews.rectifiedLeft.name in self._conf.args.show, - useRectifiedRight=Previews.rectifiedRight.name in self._conf.args.show, + useDepth=Previews.depth.name in self._conf.args.show or Previews.depthRaw.name in self._conf.args.show and self._consumeFrames, + useDisparity=Previews.disparity.name in self._conf.args.show or Previews.disparityColor.name in self._conf.args.show and self._consumeFrames, + useRectifiedLeft=Previews.rectifiedLeft.name in self._conf.args.show and self._consumeFrames, + useRectifiedRight=Previews.rectifiedRight.name in self._conf.args.show and self._consumeFrames, ) self._encManager = None @@ -269,10 +269,11 @@ def setup(self, conf: ConfigManager): sbbScaleFactor=self._conf.args.sbbScaleFactor, fullFov=not self._conf.args.disableFullFovNn, ) - self._pm.addNn(nn=self._nn, xoutNnInput=Previews.nnInput.name in self._conf.args.show, + self._pm.addNn(nn=self._nn, xoutNnInput=Previews.nnInput.name in self._conf.args.show and self._consumeFrames, xoutSbb=self._conf.args.spatialBoundingBox and self._conf.useDepth) def run(self): + self.onPipeline(self._pm.pipeline, self._pm.nodes) self._device.startPipeline(self._pm.pipeline) self._pm.createDefaultQueues(self._device) if self._conf.useNN: @@ -307,7 +308,8 @@ def run(self): if any(self._cameraConfig.values()): self._updateCameraConfigs() - self._pv.createQueues(self._device, self._createQueueCallback) + if self._consumeFrames: + self._pv.createQueues(self._device, self._createQueueCallback) if self._encManager is not None: self._encManager.createDefaultQueues(self._device) @@ -359,7 +361,8 @@ def loop(self): self.timer = time.monotonic() if self._conf.useCamera: - self._pv.prepareFrames(callback=self.onNewFrame) + if self._consumeFrames: + self._pv.prepareFrames(callback=self.onNewFrame) if self._encManager is not None: self._encManager.parseQueues() @@ -552,457 +555,37 @@ def _printSysInfo(self, info): print(','.join(map(str, data.values())), file=self._reportFile) -def prepareConfManager(in_args): - confManager = ConfigManager(in_args) - confManager.linuxCheckApplyUsbRules() - if not confManager.useCamera: - if str(confManager.args.video).startswith('https'): - confManager.args.video = downloadYTVideo(confManager.args.video, DEPTHAI_VIDEOS) - print("Youtube video downloaded.") - if not Path(confManager.args.video).exists(): - raise ValueError("Path {} does not exists!".format(confManager.args.video)) - return confManager - - -def runQt(): - from gui.main import DemoQtGui - from PyQt5.QtWidgets import QMessageBox - from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, QThreadPool - - - class WorkerSignals(QObject): - updateConfSignal = pyqtSignal(list) - updateDownloadProgressSignal = pyqtSignal(int, int) - updatePreviewSignal = pyqtSignal(np.ndarray) - setDataSignal = pyqtSignal(list) - exitSignal = pyqtSignal() - errorSignal = pyqtSignal(str) - - class Worker(QRunnable): - def __init__(self, instance, parent, conf, selectedPreview=None): - super(Worker, self).__init__() - self.running = False - self.selectedPreview = selectedPreview - self.instance = instance - self.parent = parent - self.conf = conf - self.callback_module = loadModule(conf.args.callback) - self.file_callbacks = { - callbackName: getattr(self.callback_module, callbackName) - for callbackName in ["shouldRun", "onNewFrame", "onShowFrame", "onNn", "onReport", "onSetup", "onTeardown", "onIter"] - if callable(getattr(self.callback_module, callbackName, None)) - } - self.instance.setCallbacks(**self.file_callbacks) - self.signals = WorkerSignals() - self.signals.exitSignal.connect(self.terminate) - self.signals.updateConfSignal.connect(self.updateConf) - - - def run(self): - self.running = True - self.signals.setDataSignal.emit(["restartRequired", False]) - self.instance.setCallbacks(shouldRun=self.shouldRun, onShowFrame=self.onShowFrame, onSetup=self.onSetup, onAppSetup=self.onAppSetup, onAppStart=self.onAppStart, showDownloadProgress=self.showDownloadProgress) - self.conf.args.bandwidth = "auto" - if self.conf.args.deviceId is None: - devices = dai.Device.getAllAvailableDevices() - if len(devices) > 0: - defaultDevice = next(map( - lambda info: info.getMxId(), - filter(lambda info: info.desc.protocol == dai.XLinkProtocol.X_LINK_USB_VSC, devices) - ), None) - if defaultDevice is None: - defaultDevice = devices[0].getMxId() - self.conf.args.deviceId = defaultDevice - if Previews.color.name not in self.conf.args.show: - self.conf.args.show.append(Previews.color.name) - if Previews.nnInput.name not in self.conf.args.show: - self.conf.args.show.append(Previews.nnInput.name) - if Previews.depth.name not in self.conf.args.show and Previews.disparityColor.name not in self.conf.args.show: - self.conf.args.show.append(Previews.depth.name) - if Previews.depthRaw.name not in self.conf.args.show and Previews.disparity.name not in self.conf.args.show: - self.conf.args.show.append(Previews.depthRaw.name) - if Previews.left.name not in self.conf.args.show: - self.conf.args.show.append(Previews.left.name) - if Previews.rectifiedLeft.name not in self.conf.args.show: - self.conf.args.show.append(Previews.rectifiedLeft.name) - if Previews.right.name not in self.conf.args.show: - self.conf.args.show.append(Previews.right.name) - if Previews.rectifiedRight.name not in self.conf.args.show: - self.conf.args.show.append(Previews.rectifiedRight.name) - try: - self.instance.run_all(self.conf) - except KeyboardInterrupt: - sys.exit(0) - except Exception as ex: - self.onError(ex) - - def terminate(self): - self.running = False - self.signals.setDataSignal.emit(["restartRequired", False]) - - - def updateConf(self, argsList): - self.conf.args = argparse.Namespace(**dict(argsList)) - - def onError(self, ex: Exception): - self.signals.errorSignal.emit(''.join(traceback.format_tb(ex.__traceback__) + [str(ex)])) - self.signals.setDataSignal.emit(["restartRequired", True]) - - def shouldRun(self): - if "shouldRun" in self.file_callbacks: - return self.running and self.file_callbacks["shouldRun"]() - return self.running - - def onShowFrame(self, frame, source): - if "onShowFrame" in self.file_callbacks: - self.file_callbacks["onShowFrame"](frame, source) - if source == self.selectedPreview: - self.signals.updatePreviewSignal.emit(frame) - - def onAppSetup(self, app): - setupFrame = createBlankFrame(500, 500) - cv2.putText(setupFrame, "Preparing {} app...".format(app.appName), (150, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) - cv2.putText(setupFrame, "Preparing {} app...".format(app.appName), (150, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) - self.signals.updatePreviewSignal.emit(setupFrame) - - def onAppStart(self, app): - setupFrame = createBlankFrame(500, 500) - cv2.putText(setupFrame, "Running {} app... (check console)".format(app.appName), (100, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) - cv2.putText(setupFrame, "Running {} app... (check console)".format(app.appName), (100, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) - self.signals.updatePreviewSignal.emit(setupFrame) - - def showDownloadProgress(self, curr, total): - self.signals.updateDownloadProgressSignal.emit(curr, total) - - def onSetup(self, instance): - if "onSetup" in self.file_callbacks: - self.file_callbacks["onSetup"](instance) - self.signals.updateConfSignal.emit(list(vars(self.conf.args).items())) - self.signals.setDataSignal.emit(["previewChoices", self.conf.args.show]) - devices = [self.instance._deviceInfo.getMxId()] + list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())) - self.signals.setDataSignal.emit(["deviceChoices", devices]) - if instance._nnManager is not None: - self.signals.setDataSignal.emit(["countLabels", instance._nnManager._labels]) - else: - self.signals.setDataSignal.emit(["countLabels", []]) - self.signals.setDataSignal.emit(["depthEnabled", self.conf.useDepth]) - self.signals.setDataSignal.emit(["statisticsAccepted", self.instance.metrics is not None]) - self.signals.setDataSignal.emit(["modelChoices", sorted(self.conf.getAvailableZooModels(), key=cmp_to_key(lambda a, b: -1 if a == "mobilenet-ssd" else 1 if b == "mobilenet-ssd" else -1 if a < b else 1))]) - - - class GuiApp(DemoQtGui): - def __init__(self): - super().__init__() - self.confManager = prepareConfManager(args) - self.running = False - self.selectedPreview = self.confManager.args.show[0] if len(self.confManager.args.show) > 0 else "color" - self.useDisparity = False - self.dataInitialized = False - self.appInitialized = False - self.threadpool = QThreadPool() - self._demoInstance = Demo(displayFrames=False) - - def updateArg(self, arg_name, arg_value, shouldUpdate=True): - setattr(self.confManager.args, arg_name, arg_value) - if shouldUpdate: - self.worker.signals.setDataSignal.emit(["restartRequired", True]) - - - def showError(self, error): - print(error, file=sys.stderr) - msgBox = QMessageBox() - msgBox.setIcon(QMessageBox.Critical) - msgBox.setText(error) - msgBox.setWindowTitle("An error occured") - msgBox.setStandardButtons(QMessageBox.Ok) - msgBox.exec() - - def setupDataCollection(self): - try: - with Path(".consent").open() as f: - accepted = json.load(f)["statistics"] - except: - accepted = True - - self._demoInstance.toggleMetrics(accepted) - - def start(self): - self.setupDataCollection() - self.running = True - self.worker = Worker(self._demoInstance, parent=self, conf=self.confManager, selectedPreview=self.selectedPreview) - self.worker.signals.updatePreviewSignal.connect(self.updatePreview) - self.worker.signals.updateDownloadProgressSignal.connect(self.updateDownloadProgress) - self.worker.signals.setDataSignal.connect(self.setData) - self.worker.signals.errorSignal.connect(self.showError) - self.threadpool.start(self.worker) - if not self.appInitialized: - self.appInitialized = True - exit_code = self.startGui() - self.stop(wait=False) - sys.exit(exit_code) - - def stop(self, wait=True): - if hasattr(self._demoInstance, "_device"): - current_mxid = self._demoInstance._device.getMxId() - else: - current_mxid = self.confManager.args.deviceId - self.worker.signals.exitSignal.emit() - self.threadpool.waitForDone(10000) - - if wait and current_mxid is not None: - start = time.time() - while time.time() - start < 30: - if current_mxid in list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())): - break - else: - time.sleep(0.1) - else: - print(f"[Warning] Device not available again after 30 seconds! MXID: {current_mxid}") - - def restartDemo(self): - self.stop() - self.start() - - def guiOnDepthConfigUpdate(self, median=None, dct=None, sigma=None, lrc=None, lrcThreshold=None): - self._demoInstance._pm.updateDepthConfig(self._demoInstance._device, median=median, dct=dct, sigma=sigma, lrc=lrc, lrcThreshold=lrcThreshold) - if median is not None: - if median == dai.MedianFilter.MEDIAN_OFF: - self.updateArg("stereoMedianSize", 0, False) - elif median == dai.MedianFilter.KERNEL_3x3: - self.updateArg("stereoMedianSize", 3, False) - elif median == dai.MedianFilter.KERNEL_5x5: - self.updateArg("stereoMedianSize", 5, False) - elif median == dai.MedianFilter.KERNEL_7x7: - self.updateArg("stereoMedianSize", 7, False) - if dct is not None: - self.updateArg("disparityConfidenceThreshold", dct, False) - if sigma is not None: - self.updateArg("sigma", sigma, False) - if lrc is not None: - self.updateArg("stereoLrCheck", lrc, False) - if lrcThreshold is not None: - self.updateArg("lrcThreshold", lrcThreshold, False) - - def guiOnCameraConfigUpdate(self, name, exposure=None, sensitivity=None, saturation=None, contrast=None, brightness=None, sharpness=None): - if exposure is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraExposure or []))) + [(name, exposure)] - self._demoInstance._cameraConfig["exposure"] = newValue - self.updateArg("cameraExposure", newValue, False) - if sensitivity is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSensitivity or []))) + [(name, sensitivity)] - self._demoInstance._cameraConfig["sensitivity"] = newValue - self.updateArg("cameraSensitivity", newValue, False) - if saturation is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSaturation or []))) + [(name, saturation)] - self._demoInstance._cameraConfig["saturation"] = newValue - self.updateArg("cameraSaturation", newValue, False) - if contrast is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraContrast or []))) + [(name, contrast)] - self._demoInstance._cameraConfig["contrast"] = newValue - self.updateArg("cameraContrast", newValue, False) - if brightness is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraBrightness or []))) + [(name, brightness)] - self._demoInstance._cameraConfig["brightness"] = newValue - self.updateArg("cameraBrightness", newValue, False) - if sharpness is not None: - newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSharpness or []))) + [(name, sharpness)] - self._demoInstance._cameraConfig["sharpness"] = newValue - self.updateArg("cameraSharpness", newValue, False) - - self._demoInstance._updateCameraConfigs() - - def guiOnDepthSetupUpdate(self, depthFrom=None, depthTo=None, subpixel=None, extended=None): - if depthFrom is not None: - self.updateArg("minDepth", depthFrom) - if depthTo is not None: - self.updateArg("maxDepth", depthTo) - if subpixel is not None: - self.updateArg("subpixel", subpixel) - if extended is not None: - self.updateArg("extendedDisparity", extended) - - def guiOnCameraSetupUpdate(self, name, fps=None, resolution=None): - if fps is not None: - if name == "color": - self.updateArg("rgbFps", fps) - else: - self.updateArg("monoFps", fps) - if resolution is not None: - if name == "color": - self.updateArg("rgbResolution", resolution) - else: - self.updateArg("monoResolution", resolution) - - def guiOnAiSetupUpdate(self, cnn=None, shave=None, source=None, fullFov=None, sbb=None, sbbFactor=None, ov=None, countLabel=None): - if cnn is not None: - self.updateArg("cnnModel", cnn) - if shave is not None: - self.updateArg("shaves", shave) - if source is not None: - self.updateArg("camera", source) - if fullFov is not None: - self.updateArg("disableFullFovNn", not fullFov) - if sbb is not None: - self.updateArg("spatialBoundingBox", sbb) - if sbbFactor is not None: - self.updateArg("sbbScaleFactor", sbbFactor) - if ov is not None: - self.updateArg("openvinoVersion", ov) - if countLabel is not None or cnn is not None: - self.updateArg("countLabel", countLabel) - - def guiOnPreviewChangeSelected(self, selected): - self.worker.selectedPreview = selected - self.selectedPreview = selected - - def guiOnSelectDevice(self, selected): - self.updateArg("deviceId", selected) - - def guiOnReloadDevices(self): - devices = list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())) - if hasattr(self._demoInstance, "_deviceInfo"): - devices.insert(0, self._demoInstance._deviceInfo.getMxId()) - self.worker.signals.setDataSignal.emit(["deviceChoices", devices]) - if len(devices) > 0: - self.worker.signals.setDataSignal.emit(["restartRequired", True]) - - def guiOnStaticticsConsent(self, value): - try: - with Path('.consent').open('w') as f: - json.dump({"statistics": value}, f) - except: - pass - self.worker.signals.setDataSignal.emit(["restartRequired", True]) - - def guiOnToggleSync(self, value): - self.updateArg("sync", value) - - def guiOnToggleColorEncoding(self, enabled, fps): - oldConfig = self.confManager.args.encode or {} - if enabled: - oldConfig["color"] = fps - elif "color" in self.confManager.args.encode: - del oldConfig["color"] - self.updateArg("encode", oldConfig) - - def guiOnToggleLeftEncoding(self, enabled, fps): - oldConfig = self.confManager.args.encode or {} - if enabled: - oldConfig["left"] = fps - elif "color" in self.confManager.args.encode: - del oldConfig["left"] - self.updateArg("encode", oldConfig) - - def guiOnToggleRightEncoding(self, enabled, fps): - oldConfig = self.confManager.args.encode or {} - if enabled: - oldConfig["right"] = fps - elif "color" in self.confManager.args.encode: - del oldConfig["right"] - self.updateArg("encode", oldConfig) - - def guiOnSelectReportingOptions(self, temp, cpu, memory): - options = [] - if temp: - options.append("temp") - if cpu: - options.append("cpu") - if memory: - options.append("memory") - self.updateArg("report", options) - - def guiOnSelectReportingPath(self, value): - self.updateArg("reportFile", value) - - def guiOnSelectEncodingPath(self, value): - self.updateArg("encodeOutput", value) - - def guiOnToggleDepth(self, value): - self.updateArg("disableDepth", not value) - selectedPreviews = [Previews.rectifiedRight.name, Previews.rectifiedLeft.name] + ([Previews.disparity.name, Previews.disparityColor.name] if self.useDisparity else [Previews.depth.name, Previews.depthRaw.name]) - depthPreviews = [Previews.rectifiedRight.name, Previews.rectifiedLeft.name, Previews.depth.name, Previews.depthRaw.name, Previews.disparity.name, Previews.disparityColor.name] - filtered = list(filter(lambda name: name not in depthPreviews, self.confManager.args.show)) - if value: - updated = filtered + selectedPreviews - if self.selectedPreview not in updated: - self.selectedPreview = updated[0] - self.updateArg("show", updated) - else: - updated = filtered + [Previews.left.name, Previews.right.name] - if self.selectedPreview not in updated: - self.selectedPreview = updated[0] - self.updateArg("show", updated) - - def guiOnToggleNN(self, value): - self.updateArg("disableNeuralNetwork", not value) - filtered = list(filter(lambda name: name != Previews.nnInput.name, self.confManager.args.show)) - if value: - updated = filtered + [Previews.nnInput.name] - if self.selectedPreview not in updated: - self.selectedPreview = updated[0] - self.updateArg("show", filtered + [Previews.nnInput.name]) - else: - if self.selectedPreview not in filtered: - self.selectedPreview = filtered[0] - self.updateArg("show", filtered) - - def guiOnRunApp(self, appName): - self.stop() - self.updateArg("app", appName, shouldUpdate=False) - self.setData(["runningApp", appName]) - self.start() - - def guiOnTerminateApp(self, appName): - self.stop() - self.updateArg("app", None, shouldUpdate=False) - self.setData(["runningApp", ""]) - self.start() - - def guiOnToggleDisparity(self, value): - self.useDisparity = value - depthPreviews = [Previews.depth.name, Previews.depthRaw.name] - disparityPreviews = [Previews.disparity.name, Previews.disparityColor.name] - if value: - filtered = list(filter(lambda name: name not in depthPreviews, self.confManager.args.show)) - updated = filtered + disparityPreviews - if self.selectedPreview not in updated: - self.selectedPreview = updated[0] - self.updateArg("show", updated) - else: - filtered = list(filter(lambda name: name not in disparityPreviews, self.confManager.args.show)) - updated = filtered + depthPreviews - if self.selectedPreview not in updated: - self.selectedPreview = updated[0] - self.updateArg("show", updated) - GuiApp().start() - - -def runOpenCv(): - confManager = prepareConfManager(args) - demo = Demo() - demo.run_all(confManager) +def runOpenCv(in_args, instance): + confManager = prepareConfManager(in_args) + instance.run_all(confManager) if __name__ == "__main__": try: if args.noSupervisor: if args.guiType == "qt": - runQt() + from gui.qt.main import runQt + runQt(args, Demo(displayFrames=False)) + elif args.guiType == "web": + from gui.web.main import runWeb + runWeb(args, Demo(displayFrames=False, consumeFrames=False)) else: args.guiType = "cv" - runOpenCv() + runOpenCv(args, Demo(displayFrames=True)) else: s = Supervisor() - if args.guiType != "cv": + if args.guiType in ("auto", "qt"): available = s.checkQtAvailability() if args.guiType == "qt" and not available: raise RuntimeError("QT backend is not available, run the script with --guiType \"cv\" to use OpenCV backend") - if args.guiType == "auto" and platform.machine() == 'aarch64': # Disable Qt by default on Jetson due to Qt issues - args.guiType = "cv" - elif available: + if available: args.guiType = "qt" - else: + if args.guiType in ("auto", "cv"): + if args.guiType == "auto" and platform.machine() == 'aarch64': # Disable Qt by default on Jetson due to Qt issues args.guiType = "cv" + args.guiType = "cv" + if args.guiType in ("auto", "web"): + args.guiType = "web" s.runDemo(args) except KeyboardInterrupt: sys.exit(0) diff --git a/depthai_helpers/arg_manager.py b/depthai_helpers/arg_manager.py index d295f0980..8135c8928 100644 --- a/depthai_helpers/arg_manager.py +++ b/depthai_helpers/arg_manager.py @@ -47,17 +47,19 @@ def orientationCast(arg): openvinoVersions = list(map(lambda name: name.replace("VERSION_", ""), filter(lambda name: name.startswith("VERSION_"), vars(dai.OpenVINO.Version)))) -_streamChoices = ("nnInput", "color", "left", "right", "depth", "depthRaw", "disparity", "disparityColor", "rectifiedLeft", "rectifiedRight") +streamChoices = ("nnInput", "color", "left", "right", "depth", "depthRaw", "disparity", "disparityColor", "rectifiedLeft", "rectifiedRight") try: import cv2 colorMaps = list(map(lambda name: name[len("COLORMAP_"):], filter(lambda name: name.startswith("COLORMAP_"), vars(cv2)))) except: colorMaps = None projectRoot = Path(__file__).parent.parent +cameraChoices = ["left", "right", "color"] +reportingChoices = ["temp", "cpu", "memory"] def parseArgs(): parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('-cam', '--camera', choices=["left", "right", "color"], default="color", help="Use one of DepthAI cameras for inference (conflicts with -vid)") + parser.add_argument('-cam', '--camera', choices=cameraChoices, default="color", help="Use one of DepthAI cameras for inference (conflicts with -vid)") parser.add_argument('-vid', '--video', type=str, help="Path to video file (or YouTube link) to be used for inference (conflicts with -cam)") parser.add_argument('-dd', '--disableDepth', action="store_true", help="Disable depth information") parser.add_argument('-dnn', '--disableNeuralNetwork', action="store_true", help="Disable neural network inference") @@ -79,7 +81,7 @@ def parseArgs(): help="Sigma value for Bilateral Filter applied on depth. Default: %(default)s") parser.add_argument("-med", "--stereoMedianSize", default=7, type=int, choices=[0, 3, 5, 7], help="Disparity / depth median filter kernel size (N x N) . 0 = filtering disabled. Default: %(default)s") - parser.add_argument('-lrc', '--stereoLrCheck', action="store_true", + parser.add_argument('-dlrc', '--disableStereoLrCheck', action="store_false", dest="stereoLrCheck", help="Enable stereo 'Left-Right check' feature.") parser.add_argument('-ext', '--extendedDisparity', action="store_true", help="Enable stereo 'Extended Disparity' feature.") @@ -101,8 +103,8 @@ def parseArgs(): help="Display spatial bounding box (ROI) when displaying spatial information. The Z coordinate get's calculated from the ROI (average)") parser.add_argument("-sbbsf", "--sbbScaleFactor", default=0.3, type=float, help="Spatial bounding box scale factor. Sometimes lower scale factor can give better depth (Z) result. Default: %(default)s") - parser.add_argument('-s', '--show', default=[], nargs="+", choices=_streamChoices, help="Choose which previews to show. Default: %(default)s") - parser.add_argument('--report', nargs="+", default=[], choices=["temp", "cpu", "memory"], help="Display device utilization data") + parser.add_argument('-s', '--show', default=[], nargs="+", choices=streamChoices, help="Choose which previews to show. Default: %(default)s") + parser.add_argument('--report', nargs="+", default=[], choices=reportingChoices, help="Display device utilization data") parser.add_argument('--reportFile', help="Save report data to specified target file in CSV format") parser.add_argument("-monor", "--monoResolution", default=400, type=int, choices=[400,720,800], help="Mono cam res height: (1280x)720, (1280x)800 or (640x)400. Default: %(default)s") @@ -119,7 +121,7 @@ def parseArgs(): "If set to \"high\", the output streams will stay uncompressed\n" "If set to \"low\", the output streams will be MJPEG-encoded\n" "If set to \"auto\" (default), the optimal bandwidth will be selected based on your connection type and speed") - parser.add_argument('-gt', '--guiType', type=str, default="auto", choices=["auto", "qt", "cv"], help="Specify GUI type of the demo. \"cv\" uses built-in OpenCV display methods, \"qt\" uses Qt to display interactive GUI. \"auto\" will use OpenCV for Raspberry Pi and Qt for other platforms") + parser.add_argument('-gt', '--guiType', type=str, default="auto", choices=["auto", "qt", "cv", "web"], help="Specify GUI type of the demo. \"cv\" uses built-in OpenCV display methods, \"qt\" uses Qt to display interactive GUI. \"auto\" will use OpenCV for Raspberry Pi and Qt for other platforms") parser.add_argument('-usbs', '--usbSpeed', type=str, default="usb3", choices=["usb2", "usb3"], help="Force USB communication speed. Default: %(default)s") parser.add_argument('-enc', '--encode', type=_comaSeparated(default=30.0, cast=float), nargs="+", default=[], help="Define which cameras to encode (record) \n" @@ -144,4 +146,6 @@ def parseArgs(): parser.add_argument('--skipVersionCheck', action="store_true", help="Disable libraries version check") parser.add_argument('--noSupervisor', action="store_true", help="Disable supervisor check") parser.add_argument('--sync', action="store_true", help="Enable frame and NN synchronization. If enabled, all frames and NN results will be synced before preview (same sequence number)") + parser.add_argument('--host', default="127.0.0.1", help="Specify host address to which web server will bind (used only with guiType set to `web`)") + parser.add_argument('--port', default=8090, type=int, help="Specify port to which web server will bind (used only with guiType set to `web`)") return parser.parse_args() diff --git a/depthai_helpers/config_manager.py b/depthai_helpers/config_manager.py index ba2dccc0e..b9bd82024 100644 --- a/depthai_helpers/config_manager.py +++ b/depthai_helpers/config_manager.py @@ -8,6 +8,7 @@ from depthai_helpers.cli_utils import cliPrint, PrintColors from depthai_sdk.previews import Previews +from depthai_sdk import downloadYTVideo DEPTHAI_ZOO = Path(__file__).parent.parent / Path(f"resources/nn/") @@ -274,3 +275,14 @@ def dispMultiplier(self): return val +def prepareConfManager(in_args): + confManager = ConfigManager(in_args) + confManager.linuxCheckApplyUsbRules() + if not confManager.useCamera: + if str(confManager.args.video).startswith('https'): + confManager.args.video = downloadYTVideo(confManager.args.video, DEPTHAI_VIDEOS) + print("Youtube video downloaded.") + if not Path(confManager.args.video).exists(): + raise ValueError("Path {} does not exists!".format(confManager.args.video)) + return confManager + diff --git a/depthai_helpers/supervisor.py b/depthai_helpers/supervisor.py index 4d92695a3..1eeff22cd 100644 --- a/depthai_helpers/supervisor.py +++ b/depthai_helpers/supervisor.py @@ -39,7 +39,7 @@ def runDemo(self, args): print("Waiting 5s for the device to be discoverable again...") time.sleep(5) args.guiType = "cv" - if args.guiType == "cv": + else: new_env = env.copy() new_env["DEPTHAI_INSTALL_SIGNAL_HANDLER"] = "0" new_args = createNewArgs(args) diff --git a/depthai_sdk/setup.py b/depthai_sdk/setup.py index 1674786b9..e3ddc81d4 100644 --- a/depthai_sdk/setup.py +++ b/depthai_sdk/setup.py @@ -7,7 +7,7 @@ setup( name='depthai-sdk', - version='1.1.6', + version='1.1.7', description='This package contains convenience classes and functions that help in most common tasks while using DepthAI API', long_description=io.open("README.md", encoding="utf-8").read(), long_description_content_type="text/markdown", @@ -19,6 +19,9 @@ packages=['depthai_sdk'], package_dir={"": "src"}, # https://stackoverflow.com/a/67238346/5494277 install_requires=required, + extras_require = { + 'turbojpeg': ["PyTurboJPEG"] + }, include_package_data=True, project_urls={ "Bug Tracker": "https://github.com/luxonis/depthai/issues", diff --git a/gui/.gitignore b/gui/.gitignore deleted file mode 100644 index fab7372d7..000000000 --- a/gui/.gitignore +++ /dev/null @@ -1,73 +0,0 @@ -# This file is used to ignore files which are generated -# ---------------------------------------------------------------------------- - -*~ -*.autosave -*.a -*.core -*.moc -*.o -*.obj -*.orig -*.rej -*.so -*.so.* -*_pch.h.cpp -*_resource.rc -*.qm -.#* -*.*# -core -!core/ -tags -.DS_Store -.directory -*.debug -Makefile* -*.prl -*.app -moc_*.cpp -ui_*.h -qrc_*.cpp -Thumbs.db -*.res -*.rc -/.qmake.cache -/.qmake.stash - -# qtcreator generated files -*.pro.user* - -# xemacs temporary files -*.flc - -# Vim temporary files -.*.swp - -# Visual Studio generated files -*.ib_pdb_index -*.idb -*.ilk -*.pdb -*.sln -*.suo -*.vcproj -*vcproj.*.*.user -*.ncb -*.sdf -*.opensdf -*.vcxproj -*vcxproj.* - -# MinGW generated files -*.Debug -*.Release - -# Python byte code -*.pyc - -# Binaries -# -------- -*.dll -*.exe - diff --git a/gui/main.py b/gui/main.py deleted file mode 100644 index 6fc5670f4..000000000 --- a/gui/main.py +++ /dev/null @@ -1,360 +0,0 @@ -# This Python file uses the following encoding: utf-8 -import sys -from pathlib import Path - -import blobconverter -import cv2 -from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType, qmlRegisterSingletonType, QQmlEngine -from PyQt5.QtQuick import QQuickPaintedItem -from PyQt5.QtGui import QImage -from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool -import depthai as dai - -# To be used on the @QmlElement decorator -# (QML_IMPORT_MINOR_VERSION is optional) -from PyQt5.QtWidgets import QApplication -from depthai_sdk import Previews, resizeLetterbox, createBlankFrame - - -class Singleton(type(QQuickPaintedItem)): - _instances = {} - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] - - -instance = None - - -# @QmlElement -class ImageWriter(QQuickPaintedItem): - frame = QImage() - - def __init__(self, parent): - super().__init__(parent) - self.setRenderTarget(QQuickPaintedItem.FramebufferObject) - self.setProperty("parent", parent) - - def paint(self, painter): - painter.drawImage(0, 0, self.frame) - - def update_frame(self, image): - self.frame = image - self.update() - - -# @QmlElement -class AppBridge(QObject): - @pyqtSlot() - def applyAndRestart(self): - instance.restartDemo() - - @pyqtSlot() - def reloadDevices(self): - instance.guiOnReloadDevices() - - @pyqtSlot(bool) - def toggleStatisticsConsent(self, value): - instance.guiOnStaticticsConsent(value) - - @pyqtSlot(bool) - def toggleSync(self, value): - instance.guiOnToggleSync(value) - - @pyqtSlot(str) - def runApp(self, appName): - instance.guiOnRunApp(appName) - - @pyqtSlot(str) - def terminateApp(self, appName): - instance.guiOnTerminateApp(appName) - - @pyqtSlot(str) - def selectDevice(self, value): - instance.guiOnSelectDevice(value) - - @pyqtSlot(bool, bool, bool) - def selectReportingOptions(self, temp, cpu, memory): - instance.guiOnSelectReportingOptions(temp, cpu, memory) - - @pyqtSlot(str) - def selectReportingPath(self, value): - instance.guiOnSelectReportingPath(value) - - @pyqtSlot(str) - def selectEncodingPath(self, value): - instance.guiOnSelectEncodingPath(value) - - @pyqtSlot(bool, int) - def toggleColorEncoding(self, enabled, fps): - instance.guiOnToggleColorEncoding(enabled, fps) - - @pyqtSlot(bool, int) - def toggleLeftEncoding(self, enabled, fps): - instance.guiOnToggleLeftEncoding(enabled, fps) - - @pyqtSlot(bool, int) - def toggleRightEncoding(self, enabled, fps): - instance.guiOnToggleRightEncoding(enabled, fps) - - @pyqtSlot(bool) - def toggleDepth(self, enabled): - instance.guiOnToggleDepth(enabled) - - @pyqtSlot(bool) - def toggleNN(self, enabled): - instance.guiOnToggleNN(enabled) - - @pyqtSlot(bool) - def toggleDisparity(self, enabled): - instance.guiOnToggleDisparity(enabled) - - -# @QmlElement -class AIBridge(QObject): - @pyqtSlot(str) - def setCnnModel(self, name): - instance.guiOnAiSetupUpdate(cnn=name) - - @pyqtSlot(int) - def setShaves(self, value): - instance.guiOnAiSetupUpdate(shave=value) - - @pyqtSlot(str) - def setModelSource(self, value): - instance.guiOnAiSetupUpdate(source=value) - - @pyqtSlot(bool) - def setFullFov(self, value): - instance.guiOnAiSetupUpdate(fullFov=value) - - @pyqtSlot(bool) - def setSbb(self, value): - instance.guiOnAiSetupUpdate(sbb=value) - - @pyqtSlot(float) - def setSbbFactor(self, value): - if instance.writer is not None: - instance.guiOnAiSetupUpdate(sbbFactor=value) - - @pyqtSlot(str) - def setOvVersion(self, state): - instance.guiOnAiSetupUpdate(ov=state.replace("VERSION_", "")) - - @pyqtSlot(str) - def setCountLabel(self, state): - instance.guiOnAiSetupUpdate(countLabel=state) - - -# @QmlElement -class PreviewBridge(QObject): - @pyqtSlot(str) - def changeSelected(self, state): - instance.guiOnPreviewChangeSelected(state) - - -# @QmlElement -class DepthBridge(QObject): - @pyqtSlot(bool) - def toggleSubpixel(self, state): - instance.guiOnDepthSetupUpdate(subpixel=state) - - @pyqtSlot(bool) - def toggleExtendedDisparity(self, state): - instance.guiOnDepthSetupUpdate(extended=state) - - @pyqtSlot(bool) - def toggleLeftRightCheck(self, state): - instance.guiOnDepthConfigUpdate(lrc=state) - - @pyqtSlot(int) - def setDisparityConfidenceThreshold(self, value): - instance.guiOnDepthConfigUpdate(dct=value) - - @pyqtSlot(int) - def setLrcThreshold(self, value): - instance.guiOnDepthConfigUpdate(lrcThreshold=value) - - @pyqtSlot(int) - def setBilateralSigma(self, value): - instance.guiOnDepthConfigUpdate(sigma=value) - - @pyqtSlot(int, int) - def setDepthRange(self, valFrom, valTo): - instance.guiOnDepthSetupUpdate(depthFrom=int(valFrom * 1000), depthTo=int(valTo * 1000)) - - @pyqtSlot(str) - def setMedianFilter(self, state): - value = getattr(dai.MedianFilter, state) - instance.guiOnDepthConfigUpdate(median=value) - - -# @QmlElement -class ColorCamBridge(QObject): - name = "color" - - @pyqtSlot(int, int) - def setIsoExposure(self, iso, exposure): - if iso > 0 and exposure > 0: - instance.guiOnCameraConfigUpdate("color", sensitivity=iso, exposure=exposure) - - @pyqtSlot(int) - def setContrast(self, value): - instance.guiOnCameraConfigUpdate("color", contrast=value) - - @pyqtSlot(int) - def setBrightness(self, value): - instance.guiOnCameraConfigUpdate("color", brightness=value) - - @pyqtSlot(int) - def setSaturation(self, value): - instance.guiOnCameraConfigUpdate("color", saturation=value) - - @pyqtSlot(int) - def setSharpness(self, value): - instance.guiOnCameraConfigUpdate("color", sharpness=value) - - @pyqtSlot(int) - def setFps(self, value): - instance.guiOnCameraSetupUpdate("color", fps=value) - - @pyqtSlot(str) - def setResolution(self, state): - if state == "THE_1080_P": - instance.guiOnCameraSetupUpdate("color", resolution=1080) - elif state == "THE_4_K": - instance.guiOnCameraSetupUpdate("color", resolution=2160) - elif state == "THE_12_MP": - instance.guiOnCameraSetupUpdate("color", resolution=3040) - - -# @QmlElement -class MonoCamBridge(QObject): - - @pyqtSlot(int, int) - def setIsoExposure(self, iso, exposure): - if iso > 0 and exposure > 0: - instance.guiOnCameraConfigUpdate("left", sensitivity=iso, exposure=exposure) - instance.guiOnCameraConfigUpdate("right", sensitivity=iso, exposure=exposure) - - @pyqtSlot(int) - def setContrast(self, value): - instance.guiOnCameraConfigUpdate("left", contrast=value) - instance.guiOnCameraConfigUpdate("right", contrast=value) - - @pyqtSlot(int) - def setBrightness(self, value): - instance.guiOnCameraConfigUpdate("left", brightness=value) - instance.guiOnCameraConfigUpdate("right", brightness=value) - - @pyqtSlot(int) - def setSaturation(self, value): - instance.guiOnCameraConfigUpdate("left", saturation=value) - instance.guiOnCameraConfigUpdate("right", saturation=value) - - @pyqtSlot(int) - def setSharpness(self, value): - instance.guiOnCameraConfigUpdate("left", sharpness=value) - instance.guiOnCameraConfigUpdate("right", sharpness=value) - - @pyqtSlot(int) - def setFps(self, value): - instance.guiOnCameraSetupUpdate("left", fps=value) - instance.guiOnCameraSetupUpdate("right", fps=value) - - @pyqtSlot(str) - def setResolution(self, state): - if state == "THE_720_P": - instance.guiOnCameraSetupUpdate("left", resolution=720) - instance.guiOnCameraSetupUpdate("right", resolution=720) - elif state == "THE_800_P": - instance.guiOnCameraSetupUpdate("left", resolution=800) - instance.guiOnCameraSetupUpdate("right", resolution=800) - elif state == "THE_400_P": - instance.guiOnCameraSetupUpdate("left", resolution=400) - instance.guiOnCameraSetupUpdate("right", resolution=400) - - -class DemoQtGui: - instance = None - writer = None - window = None - progressFrame = None - - def __init__(self): - global instance - self.app = QApplication([sys.argv[0]]) - self.engine = QQmlApplicationEngine() - self.engine.quit.connect(self.app.quit) - instance = self - qmlRegisterType(ImageWriter, 'dai.gui', 1, 0, 'ImageWriter') - qmlRegisterType(AppBridge, 'dai.gui', 1, 0, 'AppBridge') - qmlRegisterType(AIBridge, 'dai.gui', 1, 0, 'AIBridge') - qmlRegisterType(PreviewBridge, 'dai.gui', 1, 0, 'PreviewBridge') - qmlRegisterType(DepthBridge, 'dai.gui', 1, 0, 'DepthBridge') - qmlRegisterType(ColorCamBridge, 'dai.gui', 1, 0, 'ColorCamBridge') - qmlRegisterType(MonoCamBridge, 'dai.gui', 1, 0, 'MonoCamBridge') - self.engine.addImportPath(str(Path(__file__).parent / "views")) - self.engine.load(str(Path(__file__).parent / "views" / "root.qml")) - self.window = self.engine.rootObjects()[0] - if not self.engine.rootObjects(): - raise RuntimeError("Unable to start GUI - no root objects!") - - def setData(self, data): - name, value = data - self.window.setProperty(name, value) - - def updatePreview(self, frame): - w, h = int(self.writer.width()), int(self.writer.height()) - scaledFrame = resizeLetterbox(frame, (w, h)) - if len(frame.shape) == 3: - img = QImage(scaledFrame.data, w, h, frame.shape[2] * w, 29) # 29 - QImage.Format_BGR888 - else: - img = QImage(scaledFrame.data, w, h, w, 24) # 24 - QImage.Format_Grayscale8 - self.writer.update_frame(img) - - def updateDownloadProgress(self, curr, total): - frame = self.createProgressFrame(curr / total) - img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1], 29) # 29 - QImage.Format_BGR888 - self.writer.update_frame(img) - - def createProgressFrame(self, donePercentage=None): - confManager = getattr(self, "confManager", None) - w, h = int(self.writer.width()), int(self.writer.height()) - if self.progressFrame is None: - self.progressFrame = createBlankFrame(w, h) - downloadText = "Downloading model blob..." - textsize = cv2.getTextSize(downloadText, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - offset = int((w - textsize) / 2) - cv2.putText(self.progressFrame, downloadText, (offset, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) - cv2.putText(self.progressFrame, downloadText, (offset, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) - - newFrame = self.progressFrame.copy() - if donePercentage is not None: - cv2.rectangle(newFrame, (100, 300), (460, 350), (255, 255, 255), cv2.FILLED) - cv2.rectangle(newFrame, (110, 310), (int(110 + 340 * donePercentage), 340), (0, 0, 0), cv2.FILLED) - return newFrame - - def showSetupFrame(self, text): - w, h = int(self.writer.width()), int(self.writer.height()) - setupFrame = createBlankFrame(w, h) - cv2.putText(setupFrame, text, (200, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) - cv2.putText(setupFrame, text, (200, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) - img = QImage(setupFrame.data, w, h, setupFrame.shape[2] * w, 29) # 29 - QImage.Format_BGR888 - self.writer.update_frame(img) - - def startGui(self): - self.writer = self.window.findChild(QObject, "writer") - self.showSetupFrame("Starting demo...") - medianChoices = list(filter(lambda name: name.startswith('KERNEL_') or name.startswith('MEDIAN_'), vars(dai.MedianFilter).keys()))[::-1] - self.setData(["medianChoices", medianChoices]) - colorChoices = list(filter(lambda name: name[0].isupper(), vars(dai.ColorCameraProperties.SensorResolution).keys())) - self.setData(["colorResolutionChoices", colorChoices]) - monoChoices = list(filter(lambda name: name[0].isupper(), vars(dai.MonoCameraProperties.SensorResolution).keys())) - self.setData(["monoResolutionChoices", monoChoices]) - self.setData(["modelSourceChoices", [Previews.color.name, Previews.left.name, Previews.right.name]]) - versionChoices = sorted(filter(lambda name: name.startswith("VERSION_"), vars(dai.OpenVINO).keys()), reverse=True) - self.setData(["ovVersions", versionChoices]) - self.createProgressFrame() - return self.app.exec() diff --git a/gui/README.md b/gui/qt/README.md similarity index 100% rename from gui/README.md rename to gui/qt/README.md diff --git a/gui/__init__.py b/gui/qt/__init__.py similarity index 100% rename from gui/__init__.py rename to gui/qt/__init__.py diff --git a/gui/depthai_demo.pyproject b/gui/qt/depthai_demo.pyproject similarity index 100% rename from gui/depthai_demo.pyproject rename to gui/qt/depthai_demo.pyproject diff --git a/gui/qt/main.py b/gui/qt/main.py new file mode 100644 index 000000000..ced523af8 --- /dev/null +++ b/gui/qt/main.py @@ -0,0 +1,766 @@ +# This Python file uses the following encoding: utf-8 +import argparse +import json +import sys +import time +import traceback +from functools import cmp_to_key +from pathlib import Path + +import cv2 +import numpy as np +from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType +from PyQt5.QtQuick import QQuickPaintedItem +from PyQt5.QtGui import QImage +from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool +import depthai as dai + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +from PyQt5.QtWidgets import QApplication +from depthai_sdk import Previews, resizeLetterbox, createBlankFrame, loadModule + +from depthai_helpers.arg_manager import projectRoot +from depthai_helpers.config_manager import prepareConfManager + + +class Singleton(type(QQuickPaintedItem)): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +instance = None + + +# @QmlElement +class ImageWriter(QQuickPaintedItem): + frame = QImage() + + def __init__(self, parent): + super().__init__(parent) + self.setRenderTarget(QQuickPaintedItem.FramebufferObject) + self.setProperty("parent", parent) + + def paint(self, painter): + painter.drawImage(0, 0, self.frame) + + def update_frame(self, image): + self.frame = image + self.update() + + +# @QmlElement +class AppBridge(QObject): + @pyqtSlot() + def applyAndRestart(self): + instance.restartDemo() + + @pyqtSlot() + def reloadDevices(self): + instance.guiOnReloadDevices() + + @pyqtSlot(bool) + def toggleStatisticsConsent(self, value): + instance.guiOnStaticticsConsent(value) + + @pyqtSlot(bool) + def guiOnToggleSync(self, value): + self.updateArg("sync", value) + + @pyqtSlot(str) + def runApp(self, appName): + instance.guiOnRunApp(appName) + + @pyqtSlot(str) + def terminateApp(self, appName): + instance.guiOnTerminateApp(appName) + + @pyqtSlot(str) + def selectDevice(self, value): + instance.guiOnSelectDevice(value) + + @pyqtSlot(bool, bool, bool) + def selectReportingOptions(self, temp, cpu, memory): + instance.guiOnSelectReportingOptions(temp, cpu, memory) + + @pyqtSlot(str) + def selectReportingPath(self, value): + instance.guiOnSelectReportingPath(value) + + @pyqtSlot(str) + def selectEncodingPath(self, value): + instance.guiOnSelectEncodingPath(value) + + @pyqtSlot(bool, int) + def toggleColorEncoding(self, enabled, fps): + instance.guiOnToggleColorEncoding(enabled, fps) + + @pyqtSlot(bool, int) + def toggleLeftEncoding(self, enabled, fps): + instance.guiOnToggleLeftEncoding(enabled, fps) + + @pyqtSlot(bool, int) + def toggleRightEncoding(self, enabled, fps): + instance.guiOnToggleRightEncoding(enabled, fps) + + @pyqtSlot(bool) + def toggleDepth(self, enabled): + instance.guiOnToggleDepth(enabled) + + @pyqtSlot(bool) + def toggleNN(self, enabled): + instance.guiOnToggleNN(enabled) + + @pyqtSlot(bool) + def toggleDisparity(self, enabled): + instance.guiOnToggleDisparity(enabled) + + +# @QmlElement +class AIBridge(QObject): + @pyqtSlot(str) + def setCnnModel(self, name): + instance.guiOnAiSetupUpdate(cnn=name) + + @pyqtSlot(int) + def setShaves(self, value): + instance.guiOnAiSetupUpdate(shave=value) + + @pyqtSlot(str) + def setModelSource(self, value): + instance.guiOnAiSetupUpdate(source=value) + + @pyqtSlot(bool) + def setFullFov(self, value): + instance.guiOnAiSetupUpdate(fullFov=value) + + @pyqtSlot(bool) + def setSbb(self, value): + instance.guiOnAiSetupUpdate(sbb=value) + + @pyqtSlot(float) + def setSbbFactor(self, value): + if instance.writer is not None: + instance.guiOnAiSetupUpdate(sbbFactor=value) + + @pyqtSlot(str) + def setOvVersion(self, state): + instance.guiOnAiSetupUpdate(ov=state.replace("VERSION_", "")) + + @pyqtSlot(str) + def setCountLabel(self, state): + instance.guiOnAiSetupUpdate(countLabel=state) + + +# @QmlElement +class PreviewBridge(QObject): + @pyqtSlot(str) + def changeSelected(self, state): + instance.guiOnPreviewChangeSelected(state) + + +# @QmlElement +class DepthBridge(QObject): + @pyqtSlot(bool) + def toggleSubpixel(self, state): + instance.guiOnDepthSetupUpdate(subpixel=state) + + @pyqtSlot(bool) + def toggleExtendedDisparity(self, state): + instance.guiOnDepthSetupUpdate(extended=state) + + @pyqtSlot(bool) + def toggleLeftRightCheck(self, state): + instance.guiOnDepthConfigUpdate(lrc=state) + + @pyqtSlot(int) + def setDisparityConfidenceThreshold(self, value): + instance.guiOnDepthConfigUpdate(dct=value) + + @pyqtSlot(int) + def setLrcThreshold(self, value): + instance.guiOnDepthConfigUpdate(lrcThreshold=value) + + @pyqtSlot(int) + def setBilateralSigma(self, value): + instance.guiOnDepthConfigUpdate(sigma=value) + + @pyqtSlot(int, int) + def setDepthRange(self, valFrom, valTo): + instance.guiOnDepthSetupUpdate(depthFrom=int(valFrom * 1000), depthTo=int(valTo * 1000)) + + @pyqtSlot(str) + def setMedianFilter(self, state): + value = getattr(dai.MedianFilter, state) + instance.guiOnDepthConfigUpdate(median=value) + + +# @QmlElement +class ColorCamBridge(QObject): + name = "color" + + @pyqtSlot(int, int) + def setIsoExposure(self, iso, exposure): + if iso > 0 and exposure > 0: + instance.guiOnCameraConfigUpdate("color", sensitivity=iso, exposure=exposure) + + @pyqtSlot(int) + def setContrast(self, value): + instance.guiOnCameraConfigUpdate("color", contrast=value) + + @pyqtSlot(int) + def setBrightness(self, value): + instance.guiOnCameraConfigUpdate("color", brightness=value) + + @pyqtSlot(int) + def setSaturation(self, value): + instance.guiOnCameraConfigUpdate("color", saturation=value) + + @pyqtSlot(int) + def setSharpness(self, value): + instance.guiOnCameraConfigUpdate("color", sharpness=value) + + @pyqtSlot(int) + def setFps(self, value): + instance.guiOnCameraSetupUpdate("color", fps=value) + + @pyqtSlot(str) + def setResolution(self, state): + if state == "THE_1080_P": + instance.guiOnCameraSetupUpdate("color", resolution=1080) + elif state == "THE_4_K": + instance.guiOnCameraSetupUpdate("color", resolution=2160) + elif state == "THE_12_MP": + instance.guiOnCameraSetupUpdate("color", resolution=3040) + + +# @QmlElement +class MonoCamBridge(QObject): + + @pyqtSlot(int, int) + def setIsoExposure(self, iso, exposure): + if iso > 0 and exposure > 0: + instance.guiOnCameraConfigUpdate("left", sensitivity=iso, exposure=exposure) + instance.guiOnCameraConfigUpdate("right", sensitivity=iso, exposure=exposure) + + @pyqtSlot(int) + def setContrast(self, value): + instance.guiOnCameraConfigUpdate("left", contrast=value) + instance.guiOnCameraConfigUpdate("right", contrast=value) + + @pyqtSlot(int) + def setBrightness(self, value): + instance.guiOnCameraConfigUpdate("left", brightness=value) + instance.guiOnCameraConfigUpdate("right", brightness=value) + + @pyqtSlot(int) + def setSaturation(self, value): + instance.guiOnCameraConfigUpdate("left", saturation=value) + instance.guiOnCameraConfigUpdate("right", saturation=value) + + @pyqtSlot(int) + def setSharpness(self, value): + instance.guiOnCameraConfigUpdate("left", sharpness=value) + instance.guiOnCameraConfigUpdate("right", sharpness=value) + + @pyqtSlot(int) + def setFps(self, value): + instance.guiOnCameraSetupUpdate("left", fps=value) + instance.guiOnCameraSetupUpdate("right", fps=value) + + @pyqtSlot(str) + def setResolution(self, state): + if state == "THE_720_P": + instance.guiOnCameraSetupUpdate("left", resolution=720) + instance.guiOnCameraSetupUpdate("right", resolution=720) + elif state == "THE_800_P": + instance.guiOnCameraSetupUpdate("left", resolution=800) + instance.guiOnCameraSetupUpdate("right", resolution=800) + elif state == "THE_400_P": + instance.guiOnCameraSetupUpdate("left", resolution=400) + instance.guiOnCameraSetupUpdate("right", resolution=400) + + +class DemoQtGui: + instance = None + writer = None + window = None + progressFrame = None + + def __init__(self): + global instance + self.app = QApplication([sys.argv[0]]) + self.engine = QQmlApplicationEngine() + self.engine.quit.connect(self.app.quit) + instance = self + qmlRegisterType(ImageWriter, 'dai.gui', 1, 0, 'ImageWriter') + qmlRegisterType(AppBridge, 'dai.gui', 1, 0, 'AppBridge') + qmlRegisterType(AIBridge, 'dai.gui', 1, 0, 'AIBridge') + qmlRegisterType(PreviewBridge, 'dai.gui', 1, 0, 'PreviewBridge') + qmlRegisterType(DepthBridge, 'dai.gui', 1, 0, 'DepthBridge') + qmlRegisterType(ColorCamBridge, 'dai.gui', 1, 0, 'ColorCamBridge') + qmlRegisterType(MonoCamBridge, 'dai.gui', 1, 0, 'MonoCamBridge') + self.engine.addImportPath(str(Path(__file__).parent / "views")) + self.engine.load(str(Path(__file__).parent / "views" / "root.qml")) + self.window = self.engine.rootObjects()[0] + if not self.engine.rootObjects(): + raise RuntimeError("Unable to start GUI - no root objects!") + + def setData(self, data): + name, value = data + self.window.setProperty(name, value) + + def updatePreview(self, frame): + w, h = int(self.writer.width()), int(self.writer.height()) + scaledFrame = resizeLetterbox(frame, (w, h)) + if len(frame.shape) == 3: + img = QImage(scaledFrame.data, w, h, frame.shape[2] * w, 29) # 29 - QImage.Format_BGR888 + else: + img = QImage(scaledFrame.data, w, h, w, 24) # 24 - QImage.Format_Grayscale8 + self.writer.update_frame(img) + + def updateDownloadProgress(self, curr, total): + frame = self.createProgressFrame(curr / total) + img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1], 29) # 29 - QImage.Format_BGR888 + self.writer.update_frame(img) + + def createProgressFrame(self, donePercentage=None): + confManager = getattr(self, "confManager", None) + w, h = int(self.writer.width()), int(self.writer.height()) + if self.progressFrame is None: + self.progressFrame = createBlankFrame(w, h) + downloadText = "Downloading model blob..." + textsize = cv2.getTextSize(downloadText, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] + offset = int((w - textsize) / 2) + cv2.putText(self.progressFrame, downloadText, (offset, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) + cv2.putText(self.progressFrame, downloadText, (offset, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) + + newFrame = self.progressFrame.copy() + if donePercentage is not None: + cv2.rectangle(newFrame, (100, 300), (460, 350), (255, 255, 255), cv2.FILLED) + cv2.rectangle(newFrame, (110, 310), (int(110 + 340 * donePercentage), 340), (0, 0, 0), cv2.FILLED) + return newFrame + + def showSetupFrame(self, text): + w, h = int(self.writer.width()), int(self.writer.height()) + setupFrame = createBlankFrame(w, h) + cv2.putText(setupFrame, text, (200, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) + cv2.putText(setupFrame, text, (200, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) + img = QImage(setupFrame.data, w, h, setupFrame.shape[2] * w, 29) # 29 - QImage.Format_BGR888 + self.writer.update_frame(img) + + def startGui(self): + self.writer = self.window.findChild(QObject, "writer") + self.showSetupFrame("Starting demo...") + medianChoices = list(filter(lambda name: name.startswith('KERNEL_') or name.startswith('MEDIAN_'), vars(dai.MedianFilter).keys()))[::-1] + self.setData(["medianChoices", medianChoices]) + colorChoices = list(filter(lambda name: name[0].isupper(), vars(dai.ColorCameraProperties.SensorResolution).keys())) + self.setData(["colorResolutionChoices", colorChoices]) + monoChoices = list(filter(lambda name: name[0].isupper(), vars(dai.MonoCameraProperties.SensorResolution).keys())) + self.setData(["monoResolutionChoices", monoChoices]) + self.setData(["modelSourceChoices", [Previews.color.name, Previews.left.name, Previews.right.name]]) + versionChoices = sorted(filter(lambda name: name.startswith("VERSION_"), vars(dai.OpenVINO).keys()), reverse=True) + self.setData(["ovVersions", versionChoices]) + self.createProgressFrame() + return self.app.exec() + +class WorkerSignals(QObject): + updateConfSignal = pyqtSignal(list) + updateDownloadProgressSignal = pyqtSignal(int, int) + updatePreviewSignal = pyqtSignal(np.ndarray) + setDataSignal = pyqtSignal(list) + exitSignal = pyqtSignal() + errorSignal = pyqtSignal(str) + +class Worker(QRunnable): + def __init__(self, instance, parent, conf, selectedPreview=None): + super(Worker, self).__init__() + self.running = False + self.selectedPreview = selectedPreview + self.instance = instance + self.parent = parent + self.conf = conf + self.callback_module = loadModule(conf.args.callback) + self.file_callbacks = { + callbackName: getattr(self.callback_module, callbackName) + for callbackName in ["shouldRun", "onNewFrame", "onShowFrame", "onNn", "onReport", "onSetup", "onTeardown", "onIter"] + if callable(getattr(self.callback_module, callbackName, None)) + } + self.instance.setCallbacks(**self.file_callbacks) + self.signals = WorkerSignals() + self.signals.exitSignal.connect(self.terminate) + self.signals.updateConfSignal.connect(self.updateConf) + + + def run(self): + self.running = True + self.signals.setDataSignal.emit(["restartRequired", False]) + self.instance.setCallbacks(shouldRun=self.shouldRun, onShowFrame=self.onShowFrame, onSetup=self.onSetup, onAppSetup=self.onAppSetup, onAppStart=self.onAppStart, showDownloadProgress=self.showDownloadProgress) + self.conf.args.bandwidth = "auto" + if self.conf.args.deviceId is None: + devices = dai.Device.getAllAvailableDevices() + if len(devices) > 0: + defaultDevice = next(map( + lambda info: info.getMxId(), + filter(lambda info: info.desc.protocol == dai.XLinkProtocol.X_LINK_USB_VSC, devices) + ), None) + if defaultDevice is None: + defaultDevice = devices[0].getMxId() + self.conf.args.deviceId = defaultDevice + self.conf.args.show = [ + Previews.color.name, Previews.nnInput.name, Previews.depth.name, Previews.depthRaw.name, Previews.left.name, + Previews.rectifiedLeft.name, Previews.right.name, Previews.rectifiedRight.name + ] + try: + self.instance.run_all(self.conf) + except KeyboardInterrupt: + sys.exit(0) + except Exception as ex: + self.onError(ex) + + def terminate(self): + self.running = False + self.signals.setDataSignal.emit(["restartRequired", False]) + + + def updateConf(self, argsList): + self.conf.args = argparse.Namespace(**dict(argsList)) + + def onError(self, ex: Exception): + self.signals.errorSignal.emit(''.join(traceback.format_tb(ex.__traceback__) + [str(ex)])) + self.signals.setDataSignal.emit(["restartRequired", True]) + + def shouldRun(self): + if "shouldRun" in self.file_callbacks: + return self.running and self.file_callbacks["shouldRun"]() + return self.running + + def onShowFrame(self, frame, source): + if "onShowFrame" in self.file_callbacks: + self.file_callbacks["onShowFrame"](frame, source) + if source == self.selectedPreview: + self.signals.updatePreviewSignal.emit(frame) + + def onAppSetup(self, app): + setupFrame = createBlankFrame(500, 500) + cv2.putText(setupFrame, "Preparing {} app...".format(app.appName), (150, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) + cv2.putText(setupFrame, "Preparing {} app...".format(app.appName), (150, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) + self.signals.updatePreviewSignal.emit(setupFrame) + + def onAppStart(self, app): + setupFrame = createBlankFrame(500, 500) + cv2.putText(setupFrame, "Running {} app... (check console)".format(app.appName), (100, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 4, cv2.LINE_AA) + cv2.putText(setupFrame, "Running {} app... (check console)".format(app.appName), (100, 250), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) + self.signals.updatePreviewSignal.emit(setupFrame) + + def showDownloadProgress(self, curr, total): + self.signals.updateDownloadProgressSignal.emit(curr, total) + + def onSetup(self, instance): + if "onSetup" in self.file_callbacks: + self.file_callbacks["onSetup"](instance) + self.signals.updateConfSignal.emit(list(vars(self.conf.args).items())) + self.signals.setDataSignal.emit(["previewChoices", self.conf.args.show]) + devices = [self.instance._deviceInfo.getMxId()] + list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())) + self.signals.setDataSignal.emit(["deviceChoices", devices]) + if instance._nnManager is not None: + self.signals.setDataSignal.emit(["countLabels", instance._nnManager._labels]) + else: + self.signals.setDataSignal.emit(["countLabels", []]) + self.signals.setDataSignal.emit(["depthEnabled", self.conf.useDepth]) + self.signals.setDataSignal.emit(["statisticsAccepted", self.instance.metrics is not None]) + self.signals.setDataSignal.emit(["modelChoices", sorted(self.conf.getAvailableZooModels(), key=cmp_to_key(lambda a, b: -1 if a == "mobilenet-ssd" else 1 if b == "mobilenet-ssd" else -1 if a < b else 1))]) + + +class GuiApp(DemoQtGui): + def __init__(self, instance, args): + super().__init__() + self.confManager = prepareConfManager(args) + self.running = False + self.selectedPreview = self.confManager.args.show[0] if len(self.confManager.args.show) > 0 else "color" + self.useDisparity = False + self.dataInitialized = False + self.appInitialized = False + self.threadpool = QThreadPool() + self._demoInstance = instance + + def updateArg(self, arg_name, arg_value, shouldUpdate=True): + setattr(self.confManager.args, arg_name, arg_value) + if shouldUpdate: + self.worker.signals.setDataSignal.emit(["restartRequired", True]) + + + def showError(self, error): + print(error, file=sys.stderr) + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Critical) + msgBox.setText(error) + msgBox.setWindowTitle("An error occured") + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec() + + def setupDataCollection(self): + try: + with Path(projectRoot / ".consent").open() as f: + accepted = json.load(f)["statistics"] + except: + accepted = True + + self._demoInstance.toggleMetrics(accepted) + + def start(self): + self.setupDataCollection() + self.running = True + self.worker = Worker(self._demoInstance, parent=self, conf=self.confManager, selectedPreview=self.selectedPreview) + self.worker.signals.updatePreviewSignal.connect(self.updatePreview) + self.worker.signals.updateDownloadProgressSignal.connect(self.updateDownloadProgress) + self.worker.signals.setDataSignal.connect(self.setData) + self.worker.signals.errorSignal.connect(self.showError) + self.threadpool.start(self.worker) + if not self.appInitialized: + self.appInitialized = True + exit_code = self.startGui() + self.stop(wait=False) + sys.exit(exit_code) + + def stop(self, wait=True): + if hasattr(self._demoInstance, "_device"): + current_mxid = self._demoInstance._device.getMxId() + else: + current_mxid = self.confManager.args.deviceId + self.worker.signals.exitSignal.emit() + self.threadpool.waitForDone(10000) + + if wait and current_mxid is not None: + start = time.time() + while time.time() - start < 30: + if current_mxid in list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())): + break + else: + time.sleep(0.1) + else: + print(f"[Warning] Device not available again after 30 seconds! MXID: {current_mxid}") + + def restartDemo(self): + self.stop() + self.start() + + def guiOnDepthConfigUpdate(self, median=None, dct=None, sigma=None, lrc=None, lrcThreshold=None): + self._demoInstance._pm.updateDepthConfig(self._demoInstance._device, median=median, dct=dct, sigma=sigma, lrc=lrc, lrcThreshold=lrcThreshold) + if median is not None: + if median == dai.MedianFilter.MEDIAN_OFF: + self.updateArg("stereoMedianSize", 0, False) + elif median == dai.MedianFilter.KERNEL_3x3: + self.updateArg("stereoMedianSize", 3, False) + elif median == dai.MedianFilter.KERNEL_5x5: + self.updateArg("stereoMedianSize", 5, False) + elif median == dai.MedianFilter.KERNEL_7x7: + self.updateArg("stereoMedianSize", 7, False) + if dct is not None: + self.updateArg("disparityConfidenceThreshold", dct, False) + if sigma is not None: + self.updateArg("sigma", sigma, False) + if lrc is not None: + self.updateArg("stereoLrCheck", lrc, False) + if lrcThreshold is not None: + self.updateArg("lrcThreshold", lrcThreshold, False) + + def guiOnCameraConfigUpdate(self, name, exposure=None, sensitivity=None, saturation=None, contrast=None, brightness=None, sharpness=None): + if exposure is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraExposure or []))) + [(name, exposure)] + self._demoInstance._cameraConfig["exposure"] = newValue + self.updateArg("cameraExposure", newValue, False) + if sensitivity is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSensitivity or []))) + [(name, sensitivity)] + self._demoInstance._cameraConfig["sensitivity"] = newValue + self.updateArg("cameraSensitivity", newValue, False) + if saturation is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSaturation or []))) + [(name, saturation)] + self._demoInstance._cameraConfig["saturation"] = newValue + self.updateArg("cameraSaturation", newValue, False) + if contrast is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraContrast or []))) + [(name, contrast)] + self._demoInstance._cameraConfig["contrast"] = newValue + self.updateArg("cameraContrast", newValue, False) + if brightness is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraBrightness or []))) + [(name, brightness)] + self._demoInstance._cameraConfig["brightness"] = newValue + self.updateArg("cameraBrightness", newValue, False) + if sharpness is not None: + newValue = list(filter(lambda item: item[0] == name, (self.confManager.args.cameraSharpness or []))) + [(name, sharpness)] + self._demoInstance._cameraConfig["sharpness"] = newValue + self.updateArg("cameraSharpness", newValue, False) + + self._demoInstance._updateCameraConfigs() + + def guiOnDepthSetupUpdate(self, depthFrom=None, depthTo=None, subpixel=None, extended=None): + if depthFrom is not None: + self.updateArg("minDepth", depthFrom) + if depthTo is not None: + self.updateArg("maxDepth", depthTo) + if subpixel is not None: + self.updateArg("subpixel", subpixel) + if extended is not None: + self.updateArg("extendedDisparity", extended) + + def guiOnCameraSetupUpdate(self, name, fps=None, resolution=None): + if fps is not None: + if name == "color": + self.updateArg("rgbFps", fps) + else: + self.updateArg("monoFps", fps) + if resolution is not None: + if name == "color": + self.updateArg("rgbResolution", resolution) + else: + self.updateArg("monoResolution", resolution) + + def guiOnAiSetupUpdate(self, cnn=None, shave=None, source=None, fullFov=None, sbb=None, sbbFactor=None, ov=None, countLabel=None): + if cnn is not None: + self.updateArg("cnnModel", cnn) + if shave is not None: + self.updateArg("shaves", shave) + if source is not None: + self.updateArg("camera", source) + if fullFov is not None: + self.updateArg("disableFullFovNn", not fullFov) + if sbb is not None: + self.updateArg("spatialBoundingBox", sbb) + if sbbFactor is not None: + self.updateArg("sbbScaleFactor", sbbFactor) + if ov is not None: + self.updateArg("openvinoVersion", ov) + if countLabel is not None or cnn is not None: + self.updateArg("countLabel", countLabel) + + def guiOnPreviewChangeSelected(self, selected): + self.worker.selectedPreview = selected + self.selectedPreview = selected + + def guiOnSelectDevice(self, selected): + self.updateArg("deviceId", selected) + + def guiOnReloadDevices(self): + devices = list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())) + if hasattr(self._demoInstance, "_deviceInfo"): + devices.insert(0, self._demoInstance._deviceInfo.getMxId()) + self.worker.signals.setDataSignal.emit(["deviceChoices", devices]) + if len(devices) > 0: + self.worker.signals.setDataSignal.emit(["restartRequired", True]) + + def guiOnStaticticsConsent(self, value): + try: + with Path(projectRoot / ".consent").open('w') as f: + json.dump({"statistics": value}, f) + except: + pass + self.worker.signals.setDataSignal.emit(["restartRequired", True]) + + def guiOnToggleSync(self, value): + self.updateArg("sync", value) + + def guiOnToggleColorEncoding(self, enabled, fps): + oldConfig = self.confManager.args.encode or {} + if enabled: + oldConfig["color"] = fps + elif "color" in self.confManager.args.encode: + del oldConfig["color"] + self.updateArg("encode", oldConfig) + + def guiOnToggleLeftEncoding(self, enabled, fps): + oldConfig = self.confManager.args.encode or {} + if enabled: + oldConfig["left"] = fps + elif "color" in self.confManager.args.encode: + del oldConfig["left"] + self.updateArg("encode", oldConfig) + + def guiOnToggleRightEncoding(self, enabled, fps): + oldConfig = self.confManager.args.encode or {} + if enabled: + oldConfig["right"] = fps + elif "color" in self.confManager.args.encode: + del oldConfig["right"] + self.updateArg("encode", oldConfig) + + def guiOnSelectReportingOptions(self, temp, cpu, memory): + options = [] + if temp: + options.append("temp") + if cpu: + options.append("cpu") + if memory: + options.append("memory") + self.updateArg("report", options) + + def guiOnSelectReportingPath(self, value): + self.updateArg("reportFile", value) + + def guiOnSelectEncodingPath(self, value): + self.updateArg("encodeOutput", value) + + def guiOnToggleDepth(self, value): + self.updateArg("disableDepth", not value) + selectedPreviews = [Previews.rectifiedRight.name, Previews.rectifiedLeft.name] + ([Previews.disparity.name, Previews.disparityColor.name] if self.useDisparity else [Previews.depth.name, Previews.depthRaw.name]) + depthPreviews = [Previews.rectifiedRight.name, Previews.rectifiedLeft.name, Previews.depth.name, Previews.depthRaw.name, Previews.disparity.name, Previews.disparityColor.name] + filtered = list(filter(lambda name: name not in depthPreviews, self.confManager.args.show)) + if value: + updated = filtered + selectedPreviews + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", updated) + else: + updated = filtered + [Previews.left.name, Previews.right.name] + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", updated) + + def guiOnToggleNN(self, value): + self.updateArg("disableNeuralNetwork", not value) + filtered = list(filter(lambda name: name != Previews.nnInput.name, self.confManager.args.show)) + if value: + updated = filtered + [Previews.nnInput.name] + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", filtered + [Previews.nnInput.name]) + else: + if self.selectedPreview not in filtered: + self.selectedPreview = filtered[0] + self.updateArg("show", filtered) + + def guiOnRunApp(self, appName): + self.stop() + self.updateArg("app", appName, shouldUpdate=False) + self.setData(["runningApp", appName]) + self.start() + + def guiOnTerminateApp(self, appName): + self.stop() + self.updateArg("app", None, shouldUpdate=False) + self.setData(["runningApp", ""]) + self.start() + + def guiOnToggleDisparity(self, value): + self.useDisparity = value + depthPreviews = [Previews.depth.name, Previews.depthRaw.name] + disparityPreviews = [Previews.disparity.name, Previews.disparityColor.name] + if value: + filtered = list(filter(lambda name: name not in depthPreviews, self.confManager.args.show)) + updated = filtered + disparityPreviews + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", updated) + else: + filtered = list(filter(lambda name: name not in disparityPreviews, self.confManager.args.show)) + updated = filtered + depthPreviews + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", updated) + + +def runQt(args, demo_instance): + GuiApp(demo_instance, args).start() diff --git a/gui/views/AIProperties.qml b/gui/qt/views/AIProperties.qml similarity index 100% rename from gui/views/AIProperties.qml rename to gui/qt/views/AIProperties.qml diff --git a/gui/views/CameraPreview.qml b/gui/qt/views/CameraPreview.qml similarity index 100% rename from gui/views/CameraPreview.qml rename to gui/qt/views/CameraPreview.qml diff --git a/gui/views/CameraProperties.qml b/gui/qt/views/CameraProperties.qml similarity index 100% rename from gui/views/CameraProperties.qml rename to gui/qt/views/CameraProperties.qml diff --git a/gui/views/DepthProperties.qml b/gui/qt/views/DepthProperties.qml similarity index 100% rename from gui/views/DepthProperties.qml rename to gui/qt/views/DepthProperties.qml diff --git a/gui/views/MiscProperties.qml b/gui/qt/views/MiscProperties.qml similarity index 100% rename from gui/views/MiscProperties.qml rename to gui/qt/views/MiscProperties.qml diff --git a/gui/views/root.qml b/gui/qt/views/root.qml similarity index 100% rename from gui/views/root.qml rename to gui/qt/views/root.qml diff --git a/gui/web/404.html b/gui/web/404.html new file mode 100644 index 000000000..74d3f21cf --- /dev/null +++ b/gui/web/404.html @@ -0,0 +1,10 @@ + + +
+ +