diff --git a/depthai_demo.py b/depthai_demo.py index 2c08e2596..e63c0a2eb 100755 --- a/depthai_demo.py +++ b/depthai_demo.py @@ -1,99 +1,36 @@ #!/usr/bin/env python3 import os +import sys +import time +import traceback +from functools import cmp_to_key from itertools import cycle from pathlib import Path + import cv2 import depthai as dai import platform -import numpy as np - from depthai_helpers.arg_manager import parseArgs from depthai_helpers.config_manager import ConfigManager, DEPTHAI_ZOO, DEPTHAI_VIDEOS from depthai_helpers.version_check import checkRequirementsVersion -from depthai_sdk import FPSHandler, loadModule, getDeviceInfo, downloadYTVideo, Previews +from depthai_sdk import FPSHandler, loadModule, getDeviceInfo, downloadYTVideo, Previews, resizeLetterbox from depthai_sdk.managers import NNetManager, PreviewManager, PipelineManager, EncodingManager, BlobManager -DISP_CONF_MIN = int(os.getenv("DISP_CONF_MIN", 0)) -DISP_CONF_MAX = int(os.getenv("DISP_CONF_MAX", 255)) -SIGMA_MIN = int(os.getenv("SIGMA_MIN", 0)) -SIGMA_MAX = int(os.getenv("SIGMA_MAX", 250)) -LRCT_MIN = int(os.getenv("LRCT_MIN", 0)) -LRCT_MAX = int(os.getenv("LRCT_MAX", 10)) - print('Using depthai module from: ', dai.__file__) print('Depthai version installed: ', dai.__version__) -if platform.machine() not in ['armv6l', 'aarch64']: +args = parseArgs() +if not args.skipVersionCheck and platform.machine() not in ['armv6l', 'aarch64']: checkRequirementsVersion() -conf = ConfigManager(parseArgs()) -conf.linuxCheckApplyUsbRules() -if not conf.useCamera: - if str(conf.args.video).startswith('https'): - conf.args.video = downloadYTVideo(conf.args.video, DEPTHAI_VIDEOS) +confManager = ConfigManager(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(conf.args.video).exists(): - raise ValueError("Path {} does not exists!".format(conf.args.video)) - -callbacks = loadModule(conf.args.callback) -rgbRes = conf.getRgbResolution() -monoRes = conf.getMonoResolution() - - -if conf.args.reportFile: - reportFileP = Path(conf.args.reportFile).with_suffix('.csv') - reportFileP.parent.mkdir(parents=True, exist_ok=True) - reportFile = open(conf.args.reportFile, 'a') - - -def printSysInfo(info): - m = 1024 * 1024 # MiB - if not conf.args.reportFile: - if "memory" in conf.args.report: - print(f"Drr used / total - {info.ddrMemoryUsage.used / m:.2f} / {info.ddrMemoryUsage.total / m:.2f} MiB") - print(f"Cmx used / total - {info.cmxMemoryUsage.used / m:.2f} / {info.cmxMemoryUsage.total / m:.2f} MiB") - print(f"LeonCss heap used / total - {info.leonCssMemoryUsage.used / m:.2f} / {info.leonCssMemoryUsage.total / m:.2f} MiB") - print(f"LeonMss heap used / total - {info.leonMssMemoryUsage.used / m:.2f} / {info.leonMssMemoryUsage.total / m:.2f} MiB") - if "temp" in conf.args.report: - t = info.chipTemperature - print(f"Chip temperature - average: {t.average:.2f}, css: {t.css:.2f}, mss: {t.mss:.2f}, upa0: {t.upa:.2f}, upa1: {t.dss:.2f}") - if "cpu" in conf.args.report: - print(f"Cpu usage - Leon OS: {info.leonCssCpuUsage.average * 100:.2f}%, Leon RT: {info.leonMssCpuUsage.average * 100:.2f} %") - print("----------------------------------------") - else: - data = {} - if "memory" in conf.args.report: - data = { - **data, - "ddrUsed": info.ddrMemoryUsage.used, - "ddrTotal": info.ddrMemoryUsage.total, - "cmxUsed": info.cmxMemoryUsage.used, - "cmxTotal": info.cmxMemoryUsage.total, - "leonCssUsed": info.leonCssMemoryUsage.used, - "leonCssTotal": info.leonCssMemoryUsage.total, - "leonMssUsed": info.leonMssMemoryUsage.used, - "leonMssTotal": info.leonMssMemoryUsage.total, - } - if "temp" in conf.args.report: - data = { - **data, - "tempAvg": info.chipTemperature.average, - "tempCss": info.chipTemperature.css, - "tempMss": info.chipTemperature.mss, - "tempUpa0": info.chipTemperature.upa, - "tempUpa1": info.chipTemperature.dss, - } - if "cpu" in conf.args.report: - data = { - **data, - "cpuCssAvg": info.leonCssCpuUsage.average, - "cpuMssAvg": info.leonMssCpuUsage.average, - } - - if reportFile.tell() == 0: - print(','.join(data.keys()), file=reportFile) - callbacks.onReport(data) - print(','.join(map(str, data.values())), file=reportFile) + if not Path(confManager.args.video).exists(): + raise ValueError("Path {} does not exists!".format(confManager.args.video)) class Trackbars: @@ -113,307 +50,756 @@ def fn(value): Trackbars.instances[name] = {**Trackbars.instances.get(name, {}), window: defaultVal} cv2.setTrackbarPos(name, window, defaultVal) -deviceInfo = getDeviceInfo(conf.args.deviceId) -openvinoVersion = None -if conf.args.openvinoVersion: - openvinoVersion = getattr(dai.OpenVINO.Version, 'VERSION_' + conf.args.openvinoVersion) -pm = PipelineManager(openvinoVersion) - -if conf.args.xlinkChunkSize is not None: - pm.setXlinkChunkSize(conf.args.xlinkChunkSize) - -if conf.useNN: - blobManager = BlobManager( - zooDir=DEPTHAI_ZOO, - zooName=conf.getModelName(), - ) - nnManager = NNetManager(inputSize=conf.inputSize) - - if conf.getModelDir() is not None: - configPath = conf.getModelDir() / Path(conf.getModelName()).with_suffix(f".json") - nnManager.readConfig(configPath) - - nnManager.countLabel(conf.getCountLabel(nnManager)) - pm.setNnManager(nnManager) - -# Pipeline is defined, now we can connect to the device -with dai.Device(pm.pipeline.getOpenVINOVersion(), deviceInfo, usb2Mode=conf.args.usbSpeed == "usb2") as device: - if deviceInfo.desc.protocol == dai.XLinkProtocol.X_LINK_USB_VSC: - print("USB Connection speed: {}".format(device.getUsbSpeed())) - conf.adjustParamsToDevice(device) - conf.adjustPreviewToOptions() - if conf.lowBandwidth: - pm.enableLowBandwidth() - cap = cv2.VideoCapture(conf.args.video) if not conf.useCamera else None - fps = FPSHandler() if conf.useCamera else FPSHandler(cap) - - if conf.useCamera or conf.args.sync: - pv = PreviewManager(display=conf.args.show, nnSource=conf.getModelSource(), colorMap=conf.getColorMap(), - dispMultiplier=conf.dispMultiplier, mouseTracker=True, lowBandwidth=conf.lowBandwidth, - scale=conf.args.scale, sync=conf.args.sync, fpsHandler=fps) - - if conf.leftCameraEnabled: - pm.createLeftCam(monoRes, conf.args.monoFps, orientation=conf.args.cameraOrientation.get(Previews.left.name), - xout=Previews.left.name in conf.args.show and (conf.getModelSource() != "left" or not conf.args.sync) - ) - if conf.rightCameraEnabled: - pm.createRightCam(monoRes, conf.args.monoFps, orientation=conf.args.cameraOrientation.get(Previews.right.name), - xout=Previews.right.name in conf.args.show and (conf.getModelSource() != "right" or not conf.args.sync) - ) - if conf.rgbCameraEnabled: - pm.createColorCam(nnManager.inputSize if conf.useNN else conf.previewSize, rgbRes, conf.args.rgbFps, orientation=conf.args.cameraOrientation.get(Previews.color.name), - fullFov=not conf.args.disableFullFovNn, xout=Previews.color.name in conf.args.show and (conf.getModelSource() != "color" or not conf.args.sync) - ) - - if conf.useDepth: - pm.createDepth( - conf.args.disparityConfidenceThreshold, - conf.getMedianFilter(), - conf.args.sigma, - conf.args.stereoLrCheck, - conf.args.lrcThreshold, - conf.args.extendedDisparity, - conf.args.subpixel, - useDepth=Previews.depth.name in conf.args.show or Previews.depthRaw.name in conf.args.show , - useDisparity=Previews.disparity.name in conf.args.show or Previews.disparityColor.name in conf.args.show, - useRectifiedLeft=Previews.rectifiedLeft.name in conf.args.show and (conf.getModelSource() != "rectifiedLeft" or not conf.args.sync), - useRectifiedRight=Previews.rectifiedRight.name in conf.args.show and (conf.getModelSource() != "rectifiedRight" or not conf.args.sync), + +noop = lambda *a, **k: None + + +class Demo: + DISP_CONF_MIN = int(os.getenv("DISP_CONF_MIN", 0)) + DISP_CONF_MAX = int(os.getenv("DISP_CONF_MAX", 255)) + SIGMA_MIN = int(os.getenv("SIGMA_MIN", 0)) + SIGMA_MAX = int(os.getenv("SIGMA_MAX", 250)) + LRCT_MIN = int(os.getenv("LRCT_MIN", 0)) + LRCT_MAX = int(os.getenv("LRCT_MAX", 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, shouldRun = lambda: True): + self._openvinoVersion = None + self._displayFrames = displayFrames + + self.onNewFrame = onNewFrame + self.onShowFrame = onShowFrame + self.onNn = onNn + self.onReport = onReport + self.onSetup = onSetup + self.onTeardown = onTeardown + self.onIter = onIter + self.shouldRun = shouldRun + + def setCallbacks(self, onNewFrame=None, onShowFrame=None, onNn=None, onReport=None, onSetup=None, onTeardown=None, onIter=None, shouldRun=None): + if onNewFrame is not None: + self.onNewFrame = onNewFrame + if onShowFrame is not None: + self.onShowFrame = onShowFrame + if onNn is not None: + self.onNn = onNn + if onReport is not None: + self.onReport = onReport + if onSetup is not None: + self.onSetup = onSetup + if onTeardown is not None: + self.onTeardown = onTeardown + if onIter is not None: + self.onIter = onIter + if shouldRun is not None: + self.shouldRun = shouldRun + + def setup(self, conf: ConfigManager): + print("Setting up demo...") + self._conf = conf + self._rgbRes = conf.getRgbResolution() + self._monoRes = conf.getMonoResolution() + if self._conf.args.openvinoVersion: + self._openvinoVersion = getattr(dai.OpenVINO.Version, 'VERSION_' + self._conf.args.openvinoVersion) + self._deviceInfo = getDeviceInfo(self._conf.args.deviceId) + if self._conf.args.reportFile: + reportFileP = Path(self._conf.args.reportFile).with_suffix('.csv') + reportFileP.parent.mkdir(parents=True, exist_ok=True) + self._reportFile = reportFileP.open('a') + self._pm = PipelineManager(openvinoVersion=self._openvinoVersion) + + if self._conf.args.xlinkChunkSize is not None: + self._pm.setXlinkChunkSize(self._conf.args.xlinkChunkSize) + + self._nnManager = None + if self._conf.useNN: + self._blobManager = BlobManager( + zooDir=DEPTHAI_ZOO, + zooName=self._conf.getModelName(), + ) + self._nnManager = NNetManager(inputSize=self._conf.inputSize) + + if self._conf.getModelDir() is not None: + configPath = self._conf.getModelDir() / Path(self._conf.getModelName()).with_suffix(f".json") + self._nnManager.readConfig(configPath) + + self._nnManager.countLabel(self._conf.getCountLabel(self._nnManager)) + self._pm.setNnManager(self._nnManager) + + self._device = dai.Device(self._pm.pipeline.getOpenVINOVersion(), self._deviceInfo, usb2Mode=self._conf.args.usbSpeed == "usb2") + if self._deviceInfo.desc.protocol == dai.XLinkProtocol.X_LINK_USB_VSC: + print("USB Connection speed: {}".format(self._device.getUsbSpeed())) + self._conf.adjustParamsToDevice(self._device) + self._conf.adjustPreviewToOptions() + if self._conf.lowBandwidth: + self._pm.enableLowBandwidth(poeQuality=self._conf.args.poeQuality) + self._cap = cv2.VideoCapture(self._conf.args.video) if not self._conf.useCamera else None + self._fps = FPSHandler() if self._conf.useCamera else FPSHandler(self._cap) + + if self._conf.useCamera or self._conf.args.sync: + self._pv = PreviewManager(display=self._conf.args.show, nnSource=self._conf.getModelSource(), colorMap=self._conf.getColorMap(), + dispMultiplier=self._conf.dispMultiplier, mouseTracker=True, lowBandwidth=self._conf.lowBandwidth, + scale=self._conf.args.scale, sync=self._conf.args.sync, fpsHandler=self._fps, createWindows=self._displayFrames, + depthConfig=self._pm._depthConfig) + + 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 and (self._conf.getModelSource() != "left" or not self._conf.args.sync)) + 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 and (self._conf.getModelSource() != "right" or not self._conf.args.sync)) + if self._conf.rgbCameraEnabled: + self._pm.createColorCam(self._nnManager.inputSize if self._conf.useNN else self._conf.previewSize, self._rgbRes, 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 and (self._conf.getModelSource() != "color" or not self._conf.args.sync)) + + if self._conf.useDepth: + self._pm.createDepth( + self._conf.args.disparityConfidenceThreshold, + self._conf.getMedianFilter(), + self._conf.args.sigma, + self._conf.args.stereoLrCheck, + 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 and ( + self._conf.getModelSource() != "rectifiedLeft" or not self._conf.args.sync), + useRectifiedRight=Previews.rectifiedRight.name in self._conf.args.show and ( + self._conf.getModelSource() != "rectifiedRight" or not self._conf.args.sync), + ) + + self._encManager = None + if len(self._conf.args.encode) > 0: + self._encManager = EncodingManager(self._conf.args.encode, self._conf.args.encodeOutput) + self._encManager.createEncoders(self._pm) + + if len(self._conf.args.report) > 0: + self._pm.createSystemLogger() + + if self._conf.useNN: + self._nn = self._nnManager.createNN( + pipeline=self._pm.pipeline, nodes=self._pm.nodes, source=self._conf.getModelSource(), + blobPath=self._blobManager.getBlob(shaves=self._conf.shaves, openvinoVersion=self._nnManager.openvinoVersion), + useDepth=self._conf.useDepth, minDepth=self._conf.args.minDepth, maxDepth=self._conf.args.maxDepth, + sbbScaleFactor=self._conf.args.sbbScaleFactor, fullFov=not self._conf.args.disableFullFovNn, + flipDetection=self._conf.getModelSource() in ( + "rectifiedLeft", "rectifiedRight") and not self._conf.args.stereoLrCheck, ) - encManager = None - if len(conf.args.encode) > 1: - encManager = EncodingManager(conf.args.encode, conf.args.encodeOutput) - encManager.createEncoders(pm) - - if len(conf.args.report) > 0: - pm.createSystemLogger() - - if conf.useNN: - nn = nnManager.createNN( - pipeline=pm.pipeline, nodes=pm.nodes, source=conf.getModelSource(), - blobPath=blobManager.getBlob(shaves=conf.shaves, openvinoVersion=nnManager.openvinoVersion), - useDepth=conf.useDepth, minDepth=conf.args.minDepth, maxDepth=conf.args.maxDepth, - sbbScaleFactor=conf.args.sbbScaleFactor, fullFov=not conf.args.disableFullFovNn, - flipDetection=conf.getModelSource() in ("rectifiedLeft", "rectifiedRight") and not conf.args.stereoLrCheck, - ) - - pm.addNn( - nn=nn, sync=conf.args.sync, xoutNnInput=Previews.nnInput.name in conf.args.show, - useDepth=conf.useDepth, xoutSbb=conf.args.spatialBoundingBox and conf.useDepth - ) - - # Start pipeline - device.startPipeline(pm.pipeline) - pm.createDefaultQueues(device) - if conf.useNN: - nnManager.createQueues(device) - - sbbOut = device.getOutputQueue("sbb", maxSize=1, blocking=False) if conf.useNN and conf.args.spatialBoundingBox else None - logOut = device.getOutputQueue("systemLogger", maxSize=30, blocking=False) if len(conf.args.report) > 0 else None - - medianFilters = cycle([item for name, item in vars(dai.MedianFilter).items() if name.startswith('KERNEL_') or name.startswith('MEDIAN_')]) - for medFilter in medianFilters: - # move the cycle to the current median filter - if medFilter == pm._depthConfig.getMedianFilter(): - break - - if conf.useCamera: - def createQueueCallback(queueName): - if queueName in [Previews.disparityColor.name, Previews.disparity.name, Previews.depth.name, Previews.depthRaw.name]: - Trackbars.createTrackbar('Disparity confidence', queueName, DISP_CONF_MIN, DISP_CONF_MAX, conf.args.disparityConfidenceThreshold, - lambda value: pm.updateDepthConfig(device, dct=value)) - if queueName in [Previews.depthRaw.name, Previews.depth.name]: - Trackbars.createTrackbar('Bilateral sigma', queueName, SIGMA_MIN, SIGMA_MAX, conf.args.sigma, - lambda value: pm.updateDepthConfig(device, sigma=value)) - if conf.args.stereoLrCheck: - Trackbars.createTrackbar('LR-check threshold', queueName, LRCT_MIN, LRCT_MAX, conf.args.lrcThreshold, - lambda value: pm.updateDepthConfig(device, lrcThreshold=value)) - - cameras = device.getConnectedCameras() - if dai.CameraBoardSocket.LEFT in cameras and dai.CameraBoardSocket.RIGHT in cameras: - pv.collectCalibData(device) - - cameraConfig = { - "exposure": conf.args.cameraExposure, - "sensitivity": conf.args.cameraSensitivity, - "saturation": conf.args.cameraSaturation, - "contrast": conf.args.cameraContrast, - "brightness": conf.args.cameraBrightness, - "sharpness": conf.args.cameraSharpness - } - def updateCameraConfigs(): - if conf.leftCameraEnabled: - pm.updateLeftCamConfig(device, **cameraConfig) - if conf.rightCameraEnabled: - pm.updateRightCamConfig(device, **cameraConfig) - if conf.rgbCameraEnabled: - pm.updateColorCamConfig(device, **cameraConfig) - - if any(cameraConfig.values()): - updateCameraConfigs() - - pv.createQueues(device, createQueueCallback) - if encManager is not None: - encManager.createDefaultQueues(device) - elif conf.args.sync: - hostOut = device.getOutputQueue(Previews.nnInput.name, maxSize=1, blocking=False) - - seqNum = 0 - hostFrame = None - nnData = [] - sbbRois = [] - callbacks.onSetup(**locals()) - - try: - while True: - fps.nextIter() - callbacks.onIter(**locals()) - if conf.useCamera: - pv.prepareFrames(callback=callbacks.onNewFrame) - if encManager is not None: - encManager.parseQueues() - - if sbbOut is not None: - sbb = sbbOut.tryGet() - if sbb is not None: - sbbRois = sbb.getConfigData() - depthFrames = [pv.get(Previews.depthRaw.name), pv.get(Previews.depth.name)] - for depthFrame in depthFrames: - if depthFrame is None: - continue - - for roiData in sbbRois: - roi = roiData.roi.denormalize(depthFrame.shape[1], depthFrame.shape[0]) - topLeft = roi.topLeft() - bottomRight = roi.bottomRight() - # Display SBB on the disparity map - cv2.rectangle(depthFrame, (int(topLeft.x), int(topLeft.y)), (int(bottomRight.x), int(bottomRight.y)), nnManager._bboxColors[0], 2) - else: - readCorrectly, rawHostFrame = cap.read() - if not readCorrectly: + self._pm.addNn( + nn=self._nn, sync=self._conf.args.sync, xoutNnInput=Previews.nnInput.name in self._conf.args.show, + useDepth=self._conf.useDepth, xoutSbb=self._conf.args.spatialBoundingBox and self._conf.useDepth + ) + + def run(self): + self._device.startPipeline(self._pm.pipeline) + self._pm.createDefaultQueues(self._device) + if self._conf.useNN: + self._nnManager.createQueues(self._device) + + self._sbbOut = self._device.getOutputQueue("sbb", maxSize=1, blocking=False) if self._conf.useNN and self._conf.args.spatialBoundingBox else None + self._logOut = self._device.getOutputQueue("systemLogger", maxSize=30, blocking=False) if len(self._conf.args.report) > 0 else None + + if self._conf.useDepth: + self._medianFilters = cycle([item for name, item in vars(dai.MedianFilter).items() if name.startswith('KERNEL_') or name.startswith('MEDIAN_')]) + for medFilter in self._medianFilters: + # move the cycle to the current median filter + if medFilter == self._pm._depthConfig.postProcessing.median: break + else: + self._medianFilters = [] + + if self._conf.useCamera: + cameras = self._device.getConnectedCameras() + if dai.CameraBoardSocket.LEFT in cameras and dai.CameraBoardSocket.RIGHT in cameras: + self._pv.collectCalibData(self._device) + + self._cameraConfig = { + "exposure": self._conf.args.cameraExposure, + "sensitivity": self._conf.args.cameraSensitivity, + "saturation": self._conf.args.cameraSaturation, + "contrast": self._conf.args.cameraContrast, + "brightness": self._conf.args.cameraBrightness, + "sharpness": self._conf.args.cameraSharpness + } - nnManager.sendInputFrame(rawHostFrame, seqNum) - seqNum += 1 - - if not conf.args.sync: - hostFrame = rawHostFrame - fps.tick('host') - - if conf.useNN: - inNn = nnManager.outputQueue.tryGet() - if inNn is not None: - callbacks.onNn(inNn) - if not conf.useCamera and conf.args.sync: - hostFrame = Previews.nnInput.value(hostOut.get()) - nnData = nnManager.decode(inNn) - fps.tick('nn') - - if conf.useCamera: - if conf.useNN: - nnManager.draw(pv, nnData) - - def showFramesCallback(frame, name): - fps.drawFps(frame, name) - h, w = frame.shape[:2] - if name in [Previews.disparityColor.name, Previews.disparity.name, Previews.depth.name, Previews.depthRaw.name]: - text = "Median filter: {} [M]".format(pm._depthConfig.getMedianFilter().name.lstrip("KERNEL_").lstrip("MEDIAN_")) - cv2.putText(frame, text, (10, h - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 0, 4) - cv2.putText(frame, text, (10, h - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 255, 1) - elif conf.args.cameraControlls and name in [Previews.color.name, Previews.left.name, Previews.right.name]: - text = "Exposure: {} T [+] [-] G".format(cameraConfig["exposure"] if cameraConfig["exposure"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 110), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 110), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - text = "Sensitivity: {} Y [+] [-] H".format(cameraConfig["sensitivity"] if cameraConfig["sensitivity"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 90), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 90), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - text = "Saturation: {} U [+] [-] J".format(cameraConfig["saturation"] if cameraConfig["saturation"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 70), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 70), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - text = "Contrast: {} I [+] [-] K".format(cameraConfig["contrast"] if cameraConfig["contrast"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 50), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 50), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - text = "Brightness: {} O [+] [-] L".format(cameraConfig["brightness"] if cameraConfig["brightness"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 30), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 30), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - text = "Sharpness: {} P [+] [-] ;".format(cameraConfig["sharpness"] if cameraConfig["sharpness"] is not None else "auto") - label_width = cv2.getTextSize(text, cv2.FONT_HERSHEY_TRIPLEX, 0.5, 4)[0][0] - cv2.putText(frame, text, (w - label_width, h - 10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 4) - cv2.putText(frame, text, (w - label_width, h - 10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255, 255, 255), 1) - returnFrame = callbacks.onShowFrame(frame, name) - return returnFrame if returnFrame is not None else frame - pv.showFrames(callback=showFramesCallback) - elif hostFrame is not None: - debugHostFrame = hostFrame.copy() - if conf.useNN: - nnManager.draw(debugHostFrame, nnData) - fps.drawFps(debugHostFrame, "host") + if any(self._cameraConfig.values()): + self._updateCameraConfigs() + + self._pv.createQueues(self._device, self._createQueueCallback) + if self._encManager is not None: + self._encManager.createDefaultQueues(self._device) + elif self._conf.args.sync: + self._hostOut = self._device.getOutputQueue(Previews.nnInput.name, maxSize=1, blocking=False) + + self._seqNum = 0 + self._hostFrame = None + self._nnData = [] + self._sbbRois = [] + self.onSetup(self) + + try: + while self.shouldRun(): + self._fps.nextIter() + self.onIter(self) + self.loop() + except StopIteration: + pass + finally: + self.stop() + + def stop(self): + print("Stopping demo...") + self._device.close() + del self._device + self._pm.closeDefaultQueues() + if self._conf.useCamera: + self._pv.closeQueues() + if self._encManager is not None: + self._encManager.close() + if self._nnManager is not None: + self._nnManager.closeQueues() + if self._sbbOut is not None: + self._sbbOut.close() + if self._logOut is not None: + self._logOut.close() + self._fps.printStatus() + self.onTeardown(self) + + + def loop(self): + if self._conf.useCamera: + self._pv.prepareFrames(callback=self.onNewFrame) + if self._encManager is not None: + self._encManager.parseQueues() + + if self._sbbOut is not None: + sbb = self._sbbOut.tryGet() + if sbb is not None: + self._sbbRois = sbb.getConfigData() + depthFrames = [self._pv.get(Previews.depthRaw.name), self._pv.get(Previews.depth.name)] + for depthFrame in depthFrames: + if depthFrame is None: + continue + + for roiData in self._sbbRois: + roi = roiData.roi.denormalize(depthFrame.shape[1], depthFrame.shape[0]) + topLeft = roi.topLeft() + bottomRight = roi.bottomRight() + # Display SBB on the disparity map + cv2.rectangle(depthFrame, (int(topLeft.x), int(topLeft.y)), (int(bottomRight.x), int(bottomRight.y)), self._nnManager._bboxColors[0], 2) + else: + readCorrectly, rawHostFrame = self._cap.read() + if not readCorrectly: + raise StopIteration() + + self._nnManager.sendInputFrame(rawHostFrame, self._seqNum) + self._seqNum += 1 + + if not self._conf.args.sync: + self._hostFrame = rawHostFrame + self._fps.tick('host') + + if self._nnManager is not None: + inNn = self._nnManager.outputQueue.tryGet() + if inNn is not None: + self.onNn(inNn) + if not self._conf.useCamera and self._conf.args.sync: + self._hostFrame = Previews.nnInput.value(self._hostOut.get()) + self._nnData = self._nnManager.decode(inNn) + self._fps.tick('nn') + + if self._conf.useCamera: + if self._nnManager is not None: + self._nnManager.draw(self._pv, self._nnData) + self._pv.showFrames(callback=self._showFramesCallback) + elif self._hostFrame is not None: + debugHostFrame = self._hostFrame.copy() + if self._nnManager is not None: + self._nnManager.draw(debugHostFrame, self._nnData) + self._fps.drawFps(debugHostFrame, "host") + if self._displayFrames: cv2.imshow("host", debugHostFrame) - if logOut: - logs = logOut.tryGetAll() - for log in logs: - printSysInfo(log) + if self._logOut: + logs = self._logOut.tryGetAll() + for log in logs: + self._printSysInfo(log) + if self._displayFrames: key = cv2.waitKey(1) if key == ord('q'): - break + raise StopIteration() elif key == ord('m'): - nextFilter = next(medianFilters) - pm.updateDepthConfig(device, median=nextFilter) + nextFilter = next(self._medianFilters) + self._pm.updateDepthConfig(self._device, median=nextFilter) - if conf.args.cameraControlls: + if self._conf.args.cameraControlls: update = True if key == ord('t'): - cameraConfig["exposure"] = 10000 if cameraConfig["exposure"] is None else 500 if cameraConfig["exposure"] == 1 else min(cameraConfig["exposure"] + 500, 33000) - if cameraConfig["sensitivity"] is None: - cameraConfig["sensitivity"] = 800 + self._cameraConfig["exposure"] = 10000 if self._cameraConfig["exposure"] is None else 500 if self._cameraConfig["exposure"] == 1 else min(self._cameraConfig["exposure"] + 500, 33000) + if self._cameraConfig["sensitivity"] is None: + self._cameraConfig["sensitivity"] = 800 elif key == ord('g'): - cameraConfig["exposure"] = 10000 if cameraConfig["exposure"] is None else max(cameraConfig["exposure"] - 500, 1) - if cameraConfig["sensitivity"] is None: - cameraConfig["sensitivity"] = 800 + self._cameraConfig["exposure"] = 10000 if self._cameraConfig["exposure"] is None else max(self._cameraConfig["exposure"] - 500, 1) + if self._cameraConfig["sensitivity"] is None: + self._cameraConfig["sensitivity"] = 800 elif key == ord('y'): - cameraConfig["sensitivity"] = 800 if cameraConfig["sensitivity"] is None else min(cameraConfig["sensitivity"] + 50, 1600) - if cameraConfig["exposure"] is None: - cameraConfig["exposure"] = 10000 + self._cameraConfig["sensitivity"] = 800 if self._cameraConfig["sensitivity"] is None else min(self._cameraConfig["sensitivity"] + 50, 1600) + if self._cameraConfig["exposure"] is None: + self._cameraConfig["exposure"] = 10000 elif key == ord('h'): - cameraConfig["sensitivity"] = 800 if cameraConfig["sensitivity"] is None else max(cameraConfig["sensitivity"] - 50, 100) - if cameraConfig["exposure"] is None: - cameraConfig["exposure"] = 10000 + self._cameraConfig["sensitivity"] = 800 if self._cameraConfig["sensitivity"] is None else max(self._cameraConfig["sensitivity"] - 50, 100) + if self._cameraConfig["exposure"] is None: + self._cameraConfig["exposure"] = 10000 elif key == ord('u'): - cameraConfig["saturation"] = 0 if cameraConfig["saturation"] is None else min(cameraConfig["saturation"] + 1, 10) + self._cameraConfig["saturation"] = 0 if self._cameraConfig["saturation"] is None else min(self._cameraConfig["saturation"] + 1, 10) elif key == ord('j'): - cameraConfig["saturation"] = 0 if cameraConfig["saturation"] is None else max(cameraConfig["saturation"] - 1, -10) + self._cameraConfig["saturation"] = 0 if self._cameraConfig["saturation"] is None else max(self._cameraConfig["saturation"] - 1, -10) elif key == ord('i'): - cameraConfig["contrast"] = 0 if cameraConfig["contrast"] is None else min(cameraConfig["contrast"] + 1, 10) + self._cameraConfig["contrast"] = 0 if self._cameraConfig["contrast"] is None else min(self._cameraConfig["contrast"] + 1, 10) elif key == ord('k'): - cameraConfig["contrast"] = 0 if cameraConfig["contrast"] is None else max(cameraConfig["contrast"] - 1, -10) + self._cameraConfig["contrast"] = 0 if self._cameraConfig["contrast"] is None else max(self._cameraConfig["contrast"] - 1, -10) elif key == ord('o'): - cameraConfig["brightness"] = 0 if cameraConfig["brightness"] is None else min(cameraConfig["brightness"] + 1, 10) + self._cameraConfig["brightness"] = 0 if self._cameraConfig["brightness"] is None else min(self._cameraConfig["brightness"] + 1, 10) elif key == ord('l'): - cameraConfig["brightness"] = 0 if cameraConfig["brightness"] is None else max(cameraConfig["brightness"] - 1, -10) + self._cameraConfig["brightness"] = 0 if self._cameraConfig["brightness"] is None else max(self._cameraConfig["brightness"] - 1, -10) elif key == ord('p'): - cameraConfig["sharpness"] = 0 if cameraConfig["sharpness"] is None else min(cameraConfig["sharpness"] + 1, 4) + self._cameraConfig["sharpness"] = 0 if self._cameraConfig["sharpness"] is None else min(self._cameraConfig["sharpness"] + 1, 4) elif key == ord(';'): - cameraConfig["sharpness"] = 0 if cameraConfig["sharpness"] is None else max(cameraConfig["sharpness"] - 1, 0) + self._cameraConfig["sharpness"] = 0 if self._cameraConfig["sharpness"] is None else max(self._cameraConfig["sharpness"] - 1, 0) else: update = False if update: - updateCameraConfigs() - - finally: - if conf.useCamera and encManager is not None: - encManager.close() - -if conf.args.reportFile: - reportFile.close() + self._updateCameraConfigs() + + def _createQueueCallback(self, queueName): + if self._displayFrames and queueName in [Previews.disparityColor.name, Previews.disparity.name, Previews.depth.name, Previews.depthRaw.name]: + Trackbars.createTrackbar('Disparity confidence', queueName, self.DISP_CONF_MIN, self.DISP_CONF_MAX, self._conf.args.disparityConfidenceThreshold, + lambda value: self._pm.updateDepthConfig(self._device, dct=value)) + if queueName in [Previews.depthRaw.name, Previews.depth.name]: + Trackbars.createTrackbar('Bilateral sigma', queueName, self.SIGMA_MIN, self.SIGMA_MAX, self._conf.args.sigma, + lambda value: self._pm.updateDepthConfig(self._device, sigma=value)) + if self._conf.args.stereoLrCheck: + Trackbars.createTrackbar('LR-check threshold', queueName, self.LRCT_MIN, self.LRCT_MAX, self._conf.args.lrcThreshold, + lambda value: self._pm.updateDepthConfig(self._device, lrcThreshold=value)) + + def _updateCameraConfigs(self): + parsedConfig = {} + for configOption, values in self._cameraConfig.items(): + if values is not None: + for cameraName, value in values: + newConfig = { + **parsedConfig.get(cameraName, {}), + configOption: value + } + if cameraName == "all": + parsedConfig[Previews.left.name] = newConfig + parsedConfig[Previews.right.name] = newConfig + parsedConfig[Previews.color.name] = newConfig + else: + parsedConfig[cameraName] = newConfig + + if hasattr(self, "_device"): + if self._conf.leftCameraEnabled and Previews.left.name in parsedConfig: + self._pm.updateLeftCamConfig(self._device, **parsedConfig[Previews.left.name]) + if self._conf.rightCameraEnabled and Previews.right.name in parsedConfig: + self._pm.updateRightCamConfig(self._device, **parsedConfig[Previews.right.name]) + if self._conf.rgbCameraEnabled and Previews.color.name in parsedConfig: + self._pm.updateColorCamConfig(self._device, **parsedConfig[Previews.color.name]) + + def _showFramesCallback(self, frame, name): + self._fps.drawFps(frame, name) + returnFrame = self.onShowFrame(frame, name) + return returnFrame if returnFrame is not None else frame + + + def _printSysInfo(self, info): + m = 1024 * 1024 # MiB + if not hasattr(self, "_reportFile"): + if "memory" in self._conf.args.report: + print(f"Drr used / total - {info.ddrMemoryUsage.used / m:.2f} / {info.ddrMemoryUsage.total / m:.2f} MiB") + print(f"Cmx used / total - {info.cmxMemoryUsage.used / m:.2f} / {info.cmxMemoryUsage.total / m:.2f} MiB") + print(f"LeonCss heap used / total - {info.leonCssMemoryUsage.used / m:.2f} / {info.leonCssMemoryUsage.total / m:.2f} MiB") + print(f"LeonMss heap used / total - {info.leonMssMemoryUsage.used / m:.2f} / {info.leonMssMemoryUsage.total / m:.2f} MiB") + if "temp" in self._conf.args.report: + t = info.chipTemperature + print(f"Chip temperature - average: {t.average:.2f}, css: {t.css:.2f}, mss: {t.mss:.2f}, upa0: {t.upa:.2f}, upa1: {t.dss:.2f}") + if "cpu" in self._conf.args.report: + print(f"Cpu usage - Leon OS: {info.leonCssCpuUsage.average * 100:.2f}%, Leon RT: {info.leonMssCpuUsage.average * 100:.2f} %") + print("----------------------------------------") + else: + data = {} + if "memory" in self._conf.args.report: + data = { + **data, + "ddrUsed": info.ddrMemoryUsage.used, + "ddrTotal": info.ddrMemoryUsage.total, + "cmxUsed": info.cmxMemoryUsage.used, + "cmxTotal": info.cmxMemoryUsage.total, + "leonCssUsed": info.leonCssMemoryUsage.used, + "leonCssTotal": info.leonCssMemoryUsage.total, + "leonMssUsed": info.leonMssMemoryUsage.used, + "leonMssTotal": info.leonMssMemoryUsage.total, + } + if "temp" in self._conf.args.report: + data = { + **data, + "tempAvg": info.chipTemperature.average, + "tempCss": info.chipTemperature.css, + "tempMss": info.chipTemperature.mss, + "tempUpa0": info.chipTemperature.upa, + "tempUpa1": info.chipTemperature.dss, + } + if "cpu" in self._conf.args.report: + data = { + **data, + "cpuCssAvg": info.leonCssCpuUsage.average, + "cpuMssAvg": info.leonMssCpuUsage.average, + } + + if self._reportFile.tell() == 0: + print(','.join(data.keys()), file=self._reportFile) + self.onReport(data) + print(','.join(map(str, data.values())), file=self._reportFile) + + +if __name__ == "__main__": + from gui.main import DemoQtGui + from PySide6.QtGui import QImage + + from PySide6.QtCore import QRunnable, Slot, QThreadPool, QObject, Signal + from PySide6.QtWidgets import QMessageBox + + + class WorkerSignals(QObject): + updatePreviewSignal = Signal(QImage) + setDataSignal = Signal(list) + exitSignal = Signal() + errorSignal = Signal(str) + + class Worker(QRunnable): + def __init__(self, instance, parent, selectedPreview=None): + super(Worker, self).__init__() + self.running = False + self.selectedPreview = selectedPreview + self.instance = instance + self.parent = parent + self.signals = WorkerSignals() + self.signals.exitSignal.connect(self.terminate) + + @Slot() + def run(self): + self.running = True + self.signals.setDataSignal.emit(["restartRequired", False]) + self.instance.setCallbacks(shouldRun=self.shouldRun, onShowFrame=self.onShowFrame, onSetup=self.onSetup) + confManager.args.bandwidth = "auto" + if confManager.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() + confManager.args.deviceId = defaultDevice + try: + self.instance.run_all(confManager) + except Exception as ex: + self.onError(ex) + + @Slot() + def terminate(self): + self.running = False + self.signals.setDataSignal.emit(["restartRequired", False]) + + 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): + return self.running + + def onShowFrame(self, frame, source): + writerObject = self.parent.window.findChild(QObject, "writer") + w, h = int(writerObject.width()), int(writerObject.height()) + if source == self.selectedPreview: + scaledFrame = resizeLetterbox(frame, (w, h)) + if len(frame.shape) == 3: + img = QImage(scaledFrame.data, w, h, frame.shape[2] * w, QImage.Format_BGR888) + else: + img = QImage(scaledFrame.data, w, h, w, QImage.Format_Grayscale8) + self.signals.updatePreviewSignal.emit(img) + + def onSetup(self, instance): + self.signals.setDataSignal.emit(["previewChoices", confManager.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(["modelChoices", sorted(confManager.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 App(DemoQtGui): + def __init__(self): + super().__init__() + self.running = False + self.selectedPreview = confManager.args.show[0] if len(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(confManager.args, arg_name, arg_value) + if shouldUpdate: + self.worker.signals.setDataSignal.emit(["restartRequired", True]) + + @Slot(str) + 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 start(self): + self.running = True + self.worker = Worker(self._demoInstance, parent=self, selectedPreview=self.selectedPreview) + self.worker.signals.updatePreviewSignal.connect(self.updatePreview) + 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): + current_mxid = None + protocol = None + if hasattr(self._demoInstance, "_device"): + current_mxid = self._demoInstance._device.getMxId() + protocol = self._demoInstance._deviceInfo.desc.protocol + self.worker.signals.exitSignal.emit() + self.threadpool.waitForDone(100) + + if wait and current_mxid is not None and protocol == dai.XLinkProtocol.X_LINK_USB_VSC: + start = time.time() + while time.time() - start < 10: + if current_mxid in list(map(lambda info: info.getMxId(), dai.Device.getAllAvailableDevices())): + break + else: + raise RuntimeError("Device not available again after 10 seconds!") + + 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, (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, (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, (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, (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, (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, (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 guiOnToggleColorEncoding(self, enabled, fps): + oldConfig = confManager.args.encode or {} + if enabled: + oldConfig["color"] = fps + elif "color" in confManager.args.encode: + del oldConfig["color"] + self.updateArg("encode", oldConfig) + + def guiOnToggleLeftEncoding(self, enabled, fps): + oldConfig = confManager.args.encode or {} + if enabled: + oldConfig["left"] = fps + elif "color" in confManager.args.encode: + del oldConfig["left"] + self.updateArg("encode", oldConfig) + + def guiOnToggleRightEncoding(self, enabled, fps): + oldConfig = confManager.args.encode or {} + if enabled: + oldConfig["right"] = fps + elif "color" in 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, 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, 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 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, 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, confManager.args.show)) + updated = filtered + depthPreviews + if self.selectedPreview not in updated: + self.selectedPreview = updated[0] + self.updateArg("show", updated) -fps.printStatus() -callbacks.onTeardown(**locals()) \ No newline at end of file + App().start() \ No newline at end of file diff --git a/depthai_helpers/arg_manager.py b/depthai_helpers/arg_manager.py index f398bdb48..74c394402 100644 --- a/depthai_helpers/arg_manager.py +++ b/depthai_helpers/arg_manager.py @@ -131,17 +131,18 @@ def parseArgs(): "Example: -enc color right,10 left,10") parser.add_argument('-encout', '--encodeOutput', type=Path, default=projectRoot, help="Path to directory where to store encoded files. Default: %(default)s") parser.add_argument('-xls', '--xlinkChunkSize', type=int, help="Specify XLink chunk size") + parser.add_argument('-poeq', '--poeQuality', type=checkRange(1, 100), default=100, help="Specify PoE encoding video quality (1-100)") parser.add_argument('-camo', '--cameraOrientation', type=_comaSeparated(default="AUTO", cast=orientationCast), nargs="+", default=[], help=("Define cameras orientation (available: {}) \n" "Format: camera_name,camera_orientation \n" "Example: -camo color,ROTATE_180_DEG right,ROTATE_180_DEG left,ROTATE_180_DEG").format(', '.join(orientationChoices)) ) parser.add_argument("--cameraControlls", action="store_true", help="Show camera configuration options in GUI and control them using keyboard") - parser.add_argument("--cameraExposure", type=int, help="Specify camera saturation") - parser.add_argument("--cameraSensitivity", type=int, help="Specify camera sensitivity") - parser.add_argument("--cameraSaturation", type=checkRange(-10, 10), help="Specify image saturation") - parser.add_argument("--cameraContrast", type=checkRange(-10, 10), help="Specify image contrast") - parser.add_argument("--cameraBrightness", type=checkRange(-10, 10), help="Specify image brightness") - parser.add_argument("--cameraSharpness", type=checkRange(0, 4), help="Specify image sharpness") - + parser.add_argument("--cameraExposure", type=_comaSeparated("all", int), nargs="+", help="Specify camera saturation") + parser.add_argument("--cameraSensitivity", type=_comaSeparated("all", int), nargs="+", help="Specify camera sensitivity") + parser.add_argument("--cameraSaturation", type=_comaSeparated("all", int), nargs="+", help="Specify image saturation") + parser.add_argument("--cameraContrast", type=_comaSeparated("all", int), nargs="+", help="Specify image contrast") + parser.add_argument("--cameraBrightness", type=_comaSeparated("all", int), nargs="+", help="Specify image brightness") + parser.add_argument("--cameraSharpness", type=_comaSeparated("all", int), nargs="+", help="Specify image sharpness") + parser.add_argument('--skipVersionCheck', action="store_true", help="Disable libraries version check") return parser.parse_args() diff --git a/depthai_helpers/config_manager.py b/depthai_helpers/config_manager.py index 036e9b4ed..69956c34f 100644 --- a/depthai_helpers/config_manager.py +++ b/depthai_helpers/config_manager.py @@ -4,6 +4,7 @@ from pathlib import Path import cv2 import depthai as dai +import numpy as np from depthai_helpers.cli_utils import cliPrint, PrintColors from depthai_sdk.previews import Previews @@ -84,8 +85,19 @@ def getModelDir(self): if self.args.cnnModel is not None and (DEPTHAI_ZOO / self.args.cnnModel).exists(): return DEPTHAI_ZOO / self.args.cnnModel + def getAvailableZooModels(self): + def verify(path: Path): + return path.parent.name == path.stem + + def convert(path: Path): + return path.stem + + return list(map(convert, filter(verify, DEPTHAI_ZOO.rglob("**/*.json")))) + def getColorMap(self): - return getattr(cv2, "COLORMAP_{}".format(self.args.colorMap)) + cvColorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), getattr(cv2, "COLORMAP_{}".format(self.args.colorMap))) + cvColorMap[0] = [0, 0, 0] + return cvColorMap def getRgbResolution(self): if self.args.rgbResolution == 2160: @@ -104,8 +116,6 @@ def getMonoResolution(self): return dai.MonoCameraProperties.SensorResolution.THE_400_P def getMedianFilter(self): - if self.args.subpixel: - return dai.MedianFilter.MEDIAN_OFF if self.args.stereoMedianSize == 3: return dai.MedianFilter.KERNEL_3x3 elif self.args.stereoMedianSize == 5: @@ -127,22 +137,22 @@ def adjustPreviewToOptions(self): if len(self.args.show) != 0: return - if self.args.camera == "color" and Previews.color.name not in self.args.show: - self.args.show.append(Previews.color.name) + self.args.show.append(Previews.color.name) + if self.useNN: + self.args.show.append(Previews.nnInput.name) + if self.useDepth: - if self.lowBandwidth and Previews.disparityColor.name not in self.args.show: + if self.lowBandwidth: + self.args.show.append(Previews.disparity.name) self.args.show.append(Previews.disparityColor.name) - elif not self.lowBandwidth and Previews.depth.name not in self.args.show: + else: self.args.show.append(Previews.depth.name) - if self.args.camera == "left" and Previews.rectifiedLeft.name not in self.args.show: - self.args.show.append(Previews.rectifiedLeft.name) - if self.args.camera == "right" and Previews.rectifiedRight.name not in self.args.show: - self.args.show.append(Previews.rectifiedRight.name) + self.args.show.append(Previews.depthRaw.name) + self.args.show.append(Previews.rectifiedLeft.name) + self.args.show.append(Previews.rectifiedRight.name) else: - if self.args.camera == "left" and Previews.left.name not in self.args.show: - self.args.show.append(Previews.left.name) - if self.args.camera == "right" and Previews.right.name not in self.args.show: - self.args.show.append(Previews.right.name) + self.args.show.append(Previews.left.name) + self.args.show.append(Previews.right.name) def adjustParamsToDevice(self, device): deviceInfo = device.getDeviceInfo() @@ -174,13 +184,14 @@ def adjustParamsToDevice(self, device): if deviceInfo.desc.protocol != dai.XLinkProtocol.X_LINK_USB_VSC: print("Enabling low-bandwidth mode due to connection mode... (protocol: {})".format(deviceInfo.desc.protocol)) self.args.bandwidth = "low" + print("Setting PoE video quality to 50 to reduce latency...") + self.args.poeQuality = 50 elif device.getUsbSpeed() not in [dai.UsbSpeed.SUPER, dai.UsbSpeed.SUPER_PLUS]: print("Enabling low-bandwidth mode due to low USB speed... (speed: {})".format(device.getUsbSpeed())) self.args.bandwidth = "low" else: self.args.bandwidth = "high" - def linuxCheckApplyUsbRules(self): if platform.system() == 'Linux': ret = subprocess.call(['grep', '-irn', 'ATTRS{idVendor}=="03e7"', '/etc/udev/rules.d'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) diff --git a/depthai_helpers/version_check.py b/depthai_helpers/version_check.py index 1028a0792..2367a793a 100644 --- a/depthai_helpers/version_check.py +++ b/depthai_helpers/version_check.py @@ -45,7 +45,7 @@ def getVersion(module_name): def checkRequirementsVersion(): daiVersionRequired = getVersionFromRequirements('depthai', Path(__file__).parent / Path('../requirements.txt')) if daiVersionRequired is not None: - if depthai.__version__.endswith('+dev'): + if "dev" in depthai.__version__: print('Depthai development version found, skipping check.') elif daiVersionRequired != getVersion('depthai'): raise SystemExit(f"\033[1;5;31mVersion mismatch\033[0m\033[91m between installed depthai lib and the required one by the script.\033[0m \n\ diff --git a/depthai_sdk/src/depthai_sdk/managers/encoding_manager.py b/depthai_sdk/src/depthai_sdk/managers/encoding_manager.py index 6b4e5472d..7682ed7b2 100644 --- a/depthai_sdk/src/depthai_sdk/managers/encoding_manager.py +++ b/depthai_sdk/src/depthai_sdk/managers/encoding_manager.py @@ -76,6 +76,9 @@ def printManual(): for name, file in self._encodingFiles.items(): print(cmd.format(self._encodingNodes[name].getFrameRate(), file.name, str(Path(file.name).with_suffix('.mp4')))) + for queue in self._encodingQueues.values(): + queue.close() + for name, file in self._encodingFiles.items(): file.close() try: diff --git a/depthai_sdk/src/depthai_sdk/managers/nnet_manager.py b/depthai_sdk/src/depthai_sdk/managers/nnet_manager.py index 096dff05c..63f5e32bd 100644 --- a/depthai_sdk/src/depthai_sdk/managers/nnet_manager.py +++ b/depthai_sdk/src/depthai_sdk/managers/nnet_manager.py @@ -173,6 +173,7 @@ def createNN(self, pipeline, nodes, blobPath, source="color", flipDetection=Fals nodes.camRgb.preview.link(nodes.nn.input) elif self.source == "host": nodes.xinNn = pipeline.createXLinkIn() + self.nodes.xinRgbControl.setMaxDataSize(self.inputSize[0] * self.inputSize[1] * 3) nodes.xinNn.setStreamName("nnIn") nodes.xinNn.out.link(nodes.nn.input) elif self.source in ("left", "right", "rectifiedLeft", "rectifiedRight"): @@ -341,6 +342,15 @@ def createQueues(self, device): self.inputQueue = device.getInputQueue("nnIn", maxSize=1, blocking=False) self.outputQueue = device.getOutputQueue("nnOut", maxSize=1, blocking=False) + def closeQueues(self): + """ + Closes output queues created by :func:`createQueues` + """ + if self.source == "host" and self.inputQueue is not None: + self.inputQueue.close() + if self.outputQueue is not None: + self.outputQueue.close() + def sendInputFrame(self, frame, seqNum=None): """ Sends a frame into :attr:`inputQueue` object. Handles scaling down the frame, creating a proper :obj:`depthai.ImgFrame` diff --git a/depthai_sdk/src/depthai_sdk/managers/pipeline_manager.py b/depthai_sdk/src/depthai_sdk/managers/pipeline_manager.py index cad2d21f3..d58d513d6 100644 --- a/depthai_sdk/src/depthai_sdk/managers/pipeline_manager.py +++ b/depthai_sdk/src/depthai_sdk/managers/pipeline_manager.py @@ -10,26 +10,35 @@ class PipelineManager: and connection logic onto a set of convenience functions. """ - def __init__(self, openvinoVersion=None): + def __init__(self, openvinoVersion=None, poeQuality=100): self.openvinoVersion=openvinoVersion + self.poeQuality = poeQuality + + #: depthai.Pipeline: Ready to use requested pipeline. Can be passed to :obj:`depthai.Device` to start execution + self.pipeline = dai.Pipeline() + #: types.SimpleNamespace: Contains all nodes added to the :attr:`pipeline` object, can be used to conveniently access nodes by their name + self.nodes = SimpleNamespace() if openvinoVersion is not None: self.pipeline.setOpenVINOVersion(openvinoVersion) #: depthai.OpenVINO.Version: OpenVINO version which will be used in pipeline openvinoVersion = None + #: int, Optional: PoE encoding quality, can decrease frame quality but decrease latency + poeQuality = None #: bool: If set to :code:`True`, manager will MJPEG-encode the packets sent from device to host to lower the bandwidth usage. **Can break** if more than 3 encoded outputs requested lowBandwidth = False - #: depthai.Pipeline: Ready to use requested pipeline. Can be passed to :obj:`depthai.Device` to start execution - pipeline = dai.Pipeline() - #: types.SimpleNamespace: Contains all nodes added to the :attr:`pipeline` object, can be used to conveniently access nodes by their name - nodes = SimpleNamespace() _depthConfig = dai.StereoDepthConfig() _rgbConfig = dai.CameraControl() _leftConfig = dai.CameraControl() _rightConfig = dai.CameraControl() + _depthConfigInputQueue = None + _rgbConfigInputQueue = None + _leftConfigInputQueue = None + _rightConfigInputQueue = None + def setNnManager(self, nnManager): """ Assigns NN manager. It also syncs the pipeline versions between those two objects @@ -45,15 +54,37 @@ def setNnManager(self, nnManager): def createDefaultQueues(self, device): """ - Creates queues for all requested XLinkOut's and XLinkIn's. + Creates default queues for config updates + + Args: + device (depthai.Device): Running device instance + """ + + if hasattr(self.nodes, "stereo"): + self._depthConfigInputQueue = device.getInputQueue("stereoConfig") + if hasattr(self.nodes, "camRgb"): + self._rgbConfigInputQueue = device.getInputQueue("color_control") + if hasattr(self.nodes, "monoLeft"): + self._leftConfigInputQueue = device.getInputQueue("left_control") + if hasattr(self.nodes, "monoRight"): + self._rightConfigInputQueue = device.getInputQueue("right_control") + + def closeDefaultQueues(self): + """ + Creates default queues for config updates Args: device (depthai.Device): Running device instance """ - for xout in filter(lambda node: isinstance(node, dai.node.XLinkOut), vars(self.nodes).values()): - device.getOutputQueue(xout.getStreamName(), maxSize=1, blocking=False) - for xin in filter(lambda node: isinstance(node, dai.node.XLinkIn), vars(self.nodes).values()): - device.getInputQueue(xin.getStreamName(), maxSize=1, blocking=False) + + if self._depthConfigInputQueue is not None: + self._depthConfigInputQueue.close() + if self._rgbConfigInputQueue is not None: + self._rgbConfigInputQueue.close() + if self._leftConfigInputQueue is not None: + self._leftConfigInputQueue.close() + if self._rightConfigInputQueue is not None: + self._rightConfigInputQueue.close() def __calcEncodeableSize(self, sourceSize): w, h = sourceSize @@ -102,6 +133,7 @@ def _mjpegLink(self, node, xout, nodeOutput): manip.out.link(videnc.input) else: raise NotImplementedError("Unable to create mjpeg link for encountered node type: {}".format(type(node))) + videnc.setQuality(self.poeQuality) videnc.bitstream.link(xout.input) def createColorCam(self, previewSize=None, res=dai.ColorCameraProperties.SensorResolution.THE_1080_P, fps=30, fullFov=True, orientation: dai.CameraImageOrientation=None, xout=False): @@ -134,6 +166,7 @@ def createColorCam(self, previewSize=None, res=dai.ColorCameraProperties.SensorR else: self.nodes.camRgb.video.link(self.nodes.xoutRgb.input) self.nodes.xinRgbControl = self.pipeline.createXLinkIn() + self.nodes.xinRgbControl.setMaxDataSize(1024) self.nodes.xinRgbControl.setStreamName(Previews.color.name + "_control") self.nodes.xinRgbControl.out.link(self.nodes.camRgb.inputControl) @@ -163,6 +196,7 @@ def createLeftCam(self, res=dai.MonoCameraProperties.SensorResolution.THE_720_P, else: self.nodes.monoLeft.out.link(self.nodes.xoutLeft.input) self.nodes.xinLeftControl = self.pipeline.createXLinkIn() + self.nodes.xinLeftControl.setMaxDataSize(1024) self.nodes.xinLeftControl.setStreamName(Previews.left.name + "_control") self.nodes.xinLeftControl.out.link(self.nodes.monoLeft.inputControl) @@ -191,6 +225,7 @@ def createRightCam(self, res=dai.MonoCameraProperties.SensorResolution.THE_720_P else: self.nodes.monoRight.out.link(self.nodes.xoutRight.input) self.nodes.xinRightControl = self.pipeline.createXLinkIn() + self.nodes.xinRightControl.setMaxDataSize(1024) self.nodes.xinRightControl.setStreamName(Previews.right.name + "_control") self.nodes.xinRightControl.out.link(self.nodes.monoRight.inputControl) @@ -218,14 +253,13 @@ def createDepth(self, dct=245, median=dai.MedianFilter.KERNEL_7x7, sigma=0, lr=F self.nodes.stereo = self.pipeline.createStereoDepth() self.nodes.stereo.initialConfig.setConfidenceThreshold(dct) - self._depthConfig.setConfidenceThreshold(dct) self.nodes.stereo.initialConfig.setMedianFilter(median) - self._depthConfig.setMedianFilter(median) self.nodes.stereo.initialConfig.setBilateralFilterSigma(sigma) - self._depthConfig.setBilateralFilterSigma(sigma) self.nodes.stereo.initialConfig.setLeftRightCheckThreshold(lrcThreshold) - self._depthConfig.setLeftRightCheckThreshold(lrcThreshold) + self._depthConfig = self.nodes.stereo.initialConfig.get() + + self.nodes.stereo.setRuntimeModeSwitch(True) self.nodes.stereo.setLeftRightCheck(lr) self.nodes.stereo.setExtendedDisparity(extended) self.nodes.stereo.setSubpixel(subpixel) @@ -240,6 +274,7 @@ def createDepth(self, dct=245, median=dai.MedianFilter.KERNEL_7x7, sigma=0, lr=F self.nodes.monoRight.out.link(self.nodes.stereo.right) self.nodes.xinStereoConfig = self.pipeline.createXLinkIn() + self.nodes.xinStereoConfig.setMaxDataSize(1024) self.nodes.xinStereoConfig.setStreamName("stereoConfig") self.nodes.xinStereoConfig.out.link(self.nodes.stereo.inputConfig) @@ -289,8 +324,6 @@ def _updateCamConfig(self, configRef: dai.CameraControl, cameraName, device, exp configRef.setContrast(contrast) if brightness is not None: configRef.setBrightness(brightness) - - device.getInputQueue(cameraName + "_control").send(configRef) def updateColorCamConfig(self, device, exposure=None, sensitivity=None, saturation=None, contrast=None, brightness=None, sharpness=None): """ @@ -306,6 +339,7 @@ def updateColorCamConfig(self, device, exposure=None, sensitivity=None, saturati sharpness (int, Optional): Image sharpness (Allowed range: 0..4) """ self._updateCamConfig(self._rgbConfig, Previews.color.name, device, exposure, sensitivity, saturation, contrast, brightness, sharpness) + self._rgbConfigInputQueue.send(self._rgbConfig) def updateLeftCamConfig(self, device, exposure=None, sensitivity=None, saturation=None, contrast=None, brightness=None, sharpness=None): """ @@ -321,6 +355,7 @@ def updateLeftCamConfig(self, device, exposure=None, sensitivity=None, saturatio sharpness (int, Optional): Image sharpness (Allowed range: 0..4) """ self._updateCamConfig(self._leftConfig, Previews.left.name, device, exposure, sensitivity, saturation, contrast, brightness, sharpness) + self._leftConfigInputQueue.send(self._leftConfig) def updateRightCamConfig(self, device, exposure=None, sensitivity=None, saturation=None, contrast=None, brightness=None, sharpness=None): """ @@ -336,8 +371,9 @@ def updateRightCamConfig(self, device, exposure=None, sensitivity=None, saturati sharpness (int, Optional): Image sharpness (Allowed range: 0..4) """ self._updateCamConfig(self._rightConfig, Previews.right.name, device, exposure, sensitivity, saturation, contrast, brightness, sharpness) + self._rightConfigInputQueue.send(self._rightConfig) - def updateDepthConfig(self, device, dct=None, sigma=None, median=None, lrcThreshold=None): + def updateDepthConfig(self, device, dct=None, sigma=None, median=None, lrc=None, lrcThreshold=None): """ Updates :obj:`depthai.node.StereoDepth` node config @@ -347,18 +383,20 @@ def updateDepthConfig(self, device, dct=None, sigma=None, median=None, lrcThresh are present in the depth map. median (depthai.MedianFilter, Optional): Median filter to be applied on the depth, use with :obj:`depthai.MedianFilter.MEDIANOFF` to disable median filtering sigma (int, Optional): Sigma value for bilateral filter (0..65535). If set to :code:`0`, the filter will be disabled. + lrc (bool, Optional): Enables or disables Left-Right Check mode lrcThreshold (int, Optional): Sets the Left-Right Check threshold value (0..10) """ if dct is not None: - self._depthConfig.setConfidenceThreshold(dct) + self._depthConfig.costMatching.confidenceThreshold = dct if sigma is not None: - self._depthConfig.setBilateralFilterSigma(sigma) + self._depthConfig.postProcessing.bilateralSigmaValue = sigma if median is not None: - self._depthConfig.setMedianFilter(median) + self._depthConfig.postProcessing.median = median if lrcThreshold is not None: - self._depthConfig.setLeftRightCheckThreshold(lrcThreshold) - - device.getInputQueue("stereoConfig").send(self._depthConfig) + self._depthConfig.algorithmControl.leftRightCheckThreshold = lrcThreshold + if lrc is not None: + self._depthConfig.algorithmControl.enableLeftRightCheck = lrc + self._depthConfigInputQueue.send(self._depthConfig) def addNn(self, nn, sync=False, useDepth=False, xoutNnInput=False, xoutSbb=False): """ @@ -429,13 +467,14 @@ def createSystemLogger(self, rate=1): self.nodes.xoutSystemLogger.setStreamName("systemLogger") self.nodes.systemLogger.out.link(self.nodes.xoutSystemLogger.input) - def createEncoder(self, cameraName, encFps=30): + def createEncoder(self, cameraName, encFps=30, encQuality=100): """ Creates H.264 / H.265 video encoder (:obj:`depthai.node.VideoEncoder` instance) Args: cameraName (str): Camera name to create the encoder for encFps (int, Optional): Specify encoding FPS + encQuality (int, Optional): Specify encoding quality (1-100) Raises: ValueError: if cameraName is not a supported camera name @@ -471,6 +510,7 @@ def createEncoder(self, cameraName, encFps=30): enc = self.pipeline.createVideoEncoder() enc.setDefaultProfilePreset(*encResolution, encFps, encProfile) + enc.setQuality(encQuality) encIn.link(enc.input) setattr(self.nodes, nodeName, enc) @@ -479,11 +519,15 @@ def createEncoder(self, cameraName, encFps=30): encXout.setStreamName(xoutName) setattr(self.nodes, xoutName, encXout) - def enableLowBandwidth(self): + def enableLowBandwidth(self, poeQuality): """ - Enables low-bandwidth mode. + Enables low-bandwidth mode + + Args: + poeQuality (int, Optional): PoE encoding quality, can decrease frame quality but decrease latency """ self.lowBandwidth = True + self.poeQuality = poeQuality def setXlinkChunkSize(self, chunkSize): self.pipeline.setXLinkChunkSize(chunkSize) diff --git a/depthai_sdk/src/depthai_sdk/managers/preview_manager.py b/depthai_sdk/src/depthai_sdk/managers/preview_manager.py index 89a9da46e..62086a659 100644 --- a/depthai_sdk/src/depthai_sdk/managers/preview_manager.py +++ b/depthai_sdk/src/depthai_sdk/managers/preview_manager.py @@ -13,7 +13,7 @@ class PreviewManager: #: dict: Contains name -> frame mapping that can be used to modify specific frames directly frames = {} - def __init__(self, display=[], nnSource=None, colorMap=cv2.COLORMAP_JET, dispMultiplier=255/96, mouseTracker=False, lowBandwidth=False, scale=None, sync=False, fpsHandler=None): + def __init__(self, display=[], nnSource=None, colorMap=cv2.COLORMAP_JET, depthConfig=None, dispMultiplier=255/96, mouseTracker=False, lowBandwidth=False, scale=None, sync=False, fpsHandler=None, createWindows=True): """ Args: display (list, Optional): List of :obj:`depthai_sdk.Previews` objects representing the streams to display @@ -24,7 +24,9 @@ def __init__(self, display=[], nnSource=None, colorMap=cv2.COLORMAP_JET, dispMul colorMap (cv2 color map, Optional): Color map applied on the depth frames lowBandwidth (bool, Optional): If set to :code:`True`, will decode the received frames assuming they were encoded with MJPEG encoding scale (dict, Optional): Allows to scale down frames before preview. Useful when previewing e.g. 4K frames - dispMultiplier (float, Optional): Value used for depth <-> disparity calculations + dispMultiplier (float, Optional): Multiplier used for depth <-> disparity calculations (calculated on baseline and focal) + depthConfig (depthai.StereoDepthConfig, optional): Configuration used for depth <-> disparity calculations + createWindows (bool, Optional): If True, will create preview windows using OpenCV (enabled by default) """ self.sync = sync self.nnSource = nnSource @@ -32,9 +34,11 @@ def __init__(self, display=[], nnSource=None, colorMap=cv2.COLORMAP_JET, dispMul self.lowBandwidth = lowBandwidth self.scale = scale self.dispMultiplier = dispMultiplier + self._depthConfig = depthConfig self._fpsHandler = fpsHandler self._mouseTracker = MouseClickTracker() if mouseTracker else None self._display = display + self._createWindows = createWindows self._rawFrames = {} def collectCalibData(self, device): @@ -70,10 +74,11 @@ def createQueues(self, device, callback=None): """ self.outputQueues = [] for name in self._display: - cv2.namedWindow(name) + if self._createWindows: + cv2.namedWindow(name) if callable(callback): callback(name) - if self._mouseTracker is not None: + if self._createWindows and self._mouseTracker is not None: cv2.setMouseCallback(name, self._mouseTracker.selectPoint(name)) if name not in (Previews.disparityColor.name, Previews.depth.name): # generated on host self.outputQueues.append(device.getOutputQueue(name=name, maxSize=1, blocking=False)) @@ -83,6 +88,15 @@ def createQueues(self, device, callback=None): if Previews.depth.name in self._display and Previews.depthRaw.name not in self._display: self.outputQueues.append(device.getOutputQueue(name=Previews.depthRaw.name, maxSize=1, blocking=False)) + def closeQueues(self): + """ + Closes output queues for requested preview streams + """ + + for queue in self.outputQueues: + queue.close() + + def prepareFrames(self, blocking=False, callback=None): """ This function consumes output queues' packets and parses them to obtain ready to use frames. @@ -158,7 +172,8 @@ def showFrames(self, callback=None): newFrame = callback(frame, name) if newFrame is not None: frame = newFrame - cv2.imshow(name, frame) + if self._createWindows: + cv2.imshow(name, frame) def has(self, name): """ diff --git a/depthai_sdk/src/depthai_sdk/previews.py b/depthai_sdk/src/depthai_sdk/previews.py index 04c7d7823..99caee970 100644 --- a/depthai_sdk/src/depthai_sdk/previews.py +++ b/depthai_sdk/src/depthai_sdk/previews.py @@ -92,9 +92,9 @@ def rectifiedLeft(packet, manager=None): numpy.ndarray: Ready to use OpenCV frame """ if manager is not None and manager.lowBandwidth and not manager.sync: # TODO remove sync check once passthrough is supported for MJPEG encoding - return cv2.flip(cv2.imdecode(packet.getData(), cv2.IMREAD_GRAYSCALE), 1) + return cv2.imdecode(packet.getData(), cv2.IMREAD_GRAYSCALE) else: - return cv2.flip(packet.getCvFrame(), 1) + return packet.getCvFrame() @staticmethod def rectifiedRight(packet, manager=None): @@ -109,9 +109,9 @@ def rectifiedRight(packet, manager=None): numpy.ndarray: Ready to use OpenCV frame """ if manager is not None and manager.lowBandwidth: # TODO remove sync check once passthrough is supported for MJPEG encoding - return cv2.flip(cv2.imdecode(packet.getData(), cv2.IMREAD_GRAYSCALE), 1) + return cv2.imdecode(packet.getData(), cv2.IMREAD_GRAYSCALE) else: - return cv2.flip(packet.getCvFrame(), 1) + return packet.getCvFrame() @staticmethod def depthRaw(packet, manager=None): @@ -143,6 +143,13 @@ def depth(depthRaw, manager=None): Returns: numpy.ndarray: Ready to use OpenCV frame """ + if getattr(manager, "_depthConfig", None) is None: + raise RuntimeError("Depth config has to be provided before decoding depth data") + + maxDisp = manager._depthConfig.getMaxDisparity() + subpixelLevels = pow(2, manager._depthConfig.get().algorithmControl.subpixelFractionalBits) + subpixel = manager._depthConfig.get().algorithmControl.enableSubpixel + dispIntegerLevels = maxDisp if not subpixel else maxDisp / subpixelLevels dispScaleFactor = getattr(manager, "dispScaleFactor", None) if dispScaleFactor is None: baseline = getattr(manager, 'baseline', 75) # mm @@ -151,10 +158,11 @@ def depth(depthRaw, manager=None): dispScaleFactor = baseline * focal if manager is not None: setattr(manager, "dispScaleFactor", dispScaleFactor) - - with np.errstate(divide='ignore'): # Should be safe to ignore div by zero here + with np.errstate(divide='ignore'): dispFrame = dispScaleFactor / depthRaw - dispFrame = (dispFrame * manager.dispMultiplier).astype(np.uint8) + + dispFrame = (dispFrame * 255. / dispIntegerLevels).astype(np.uint8) + return PreviewDecoder.disparityColor(dispFrame, manager) @staticmethod diff --git a/depthai_sdk/src/depthai_sdk/utils.py b/depthai_sdk/src/depthai_sdk/utils.py index 0a2322f5f..bf4f0694d 100644 --- a/depthai_sdk/src/depthai_sdk/utils.py +++ b/depthai_sdk/src/depthai_sdk/utils.py @@ -244,3 +244,21 @@ def cropToAspectRatio(frame, size): newH = (currentRatio/newRatio) * h crop = int((h - newH) / 2) return frame[crop:h-crop, :] + + +def resizeLetterbox(frame, size): + """ + Transforms the frame to meet the desired size, preserving the aspect ratio and adding black borders (letterboxing) + Args: + frame (numpy.ndarray): Source frame that will be resized + size (tuple): Desired frame size (width, heigth) + """ + border_v = 0 + border_h = 0 + if (size[1] / size[0]) >= (frame.shape[0] / frame.shape[1]): + border_v = int((((size[1] / size[0]) * frame.shape[1]) - frame.shape[0]) / 2) + else: + border_h = int((((size[0] / size[1]) * frame.shape[0]) - frame.shape[1]) / 2) + frame = cv2.copyMakeBorder(frame, border_v, border_v, border_h, border_h, cv2.BORDER_CONSTANT, 0) + return cv2.resize(frame, size) + diff --git a/gui/.gitignore b/gui/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/gui/.gitignore @@ -0,0 +1,73 @@ +# 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/README.md b/gui/README.md new file mode 100644 index 000000000..37fb35b0d --- /dev/null +++ b/gui/README.md @@ -0,0 +1,34 @@ +# DepthAI Demo GUI + + +## Local development + +> :warning: Instructions tested on Mac but should be similar in other OSes. Feel free to extend this section if it's different in your scenario :warning: + +First, download QT Everywhere 6.2.0 from [this link](https://download.qt.io/archive/qt/6.2/6.2.0/single/qt-everywhere-src-6.2.0.tar.xz.mirrorlist) (on Windows, use [this link](https://download.qt.io/archive/qt/6.2/6.2.0/single/qt-everywhere-src-6.2.0.zip.mirrorlist)) + +Next, extract the package and cd into it. Now, run the following commands: + +``` +# to prepare qt everywhere repository +$ ./configure -prefix $PWD/qtbase +# to compile the qt, may take a while (on my MacBook Air it took 4hrs) +$ cmake --build . +``` + +Now, download the [QT Creator](https://www.qt.io/product/development-tools). After downloading and installing this tool: +- go to Preferences > Kits > Qt Versions +- Click "Add" +- point to the qt everywhere directory and `qtbase/bin/qmake` +- Go to Kits +- Edit both of the kits by clicking on them, scrolling down to Qt Version and selecting the qt-everywhere version we just added +- Restart QT Creator +- go to Preferences > Qt Quick +- Click "Qt Quick Designer" +- Under "QML Emulation Layer" select second option - "Use QML emulation layer that is built...", leaving the default path +- Close Preferences +- Open .qml file and click "Design" +- A build process should start automatically. It may throw a warning that the build process is not responding, ignore +- Restart QT Creator + +Now, the setup is ready and the Designer tool can be used too diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gui/depthai_demo.pyproject b/gui/depthai_demo.pyproject new file mode 100644 index 000000000..c5510822a --- /dev/null +++ b/gui/depthai_demo.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["main.py", "views/DepthProperties.qml", "views/root.qml", "views/CameraProperties.qml", + "views/CameraPreview.qml", "views/AIProperties.qml", "views/MiscProperties.qml"] +} diff --git a/gui/main.py b/gui/main.py new file mode 100644 index 000000000..f53a0ad66 --- /dev/null +++ b/gui/main.py @@ -0,0 +1,295 @@ +# This Python file uses the following encoding: utf-8 +import sys +from pathlib import Path +from PySide6.QtCore import QObject, Slot, Signal +from PySide6.QtGui import QGuiApplication, QImage +from PySide6.QtQml import QQmlApplicationEngine, QmlElement +import depthai as dai + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +from PySide6.QtQuick import QQuickPaintedItem +from PySide6.QtWidgets import QMessageBox, QApplication +from depthai_sdk import Previews + +QML_IMPORT_NAME = "dai.gui" +QML_IMPORT_MAJOR_VERSION = 1 + +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] + + +@QmlElement +class ImageWriter(QQuickPaintedItem, metaclass=Singleton): + frame = QImage() + + def __init__(self): + super().__init__() + self.setRenderTarget(QQuickPaintedItem.FramebufferObject) + + def paint(self, painter): + painter.drawImage(0, 0, self.frame) + + def update_frame(self, image): + self.frame = image + self.update() + + +class DemoQtGui: + instance = None + writer = None + window = None + + def __init__(self): + self.app = QApplication() + self.engine = QQmlApplicationEngine() + self.setInstance() + self.engine.load(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 setInstance(self): + DemoQtGui.instance = self + + @Slot(list) + def setData(self, data): + name, value = data + self.window.setProperty(name, value) + + @Slot(QImage) + def updatePreview(self, data): + self.writer.update_frame(data) + + def startGui(self): + self.writer = ImageWriter() + 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]) + return self.app.exec() + + +@QmlElement +class AppBridge(QObject): + @Slot() + def applyAndRestart(self): + DemoQtGui.instance.restartDemo() + + @Slot() + def reloadDevices(self): + DemoQtGui.instance.guiOnReloadDevices() + + @Slot(str) + def selectDevice(self, value): + DemoQtGui.instance.guiOnSelectDevice(value) + + @Slot(bool, bool, bool) + def selectReportingOptions(self, temp, cpu, memory): + DemoQtGui.instance.guiOnSelectReportingOptions(temp, cpu, memory) + + @Slot(str) + def selectReportingPath(self, value): + DemoQtGui.instance.guiOnSelectReportingPath(value) + + @Slot(str) + def selectEncodingPath(self, value): + DemoQtGui.instance.guiOnSelectEncodingPath(value) + + @Slot(bool, int) + def toggleColorEncoding(self, enabled, fps): + DemoQtGui.instance.guiOnToggleColorEncoding(enabled, fps) + + @Slot(bool, int) + def toggleLeftEncoding(self, enabled, fps): + DemoQtGui.instance.guiOnToggleLeftEncoding(enabled, fps) + + @Slot(bool, int) + def toggleRightEncoding(self, enabled, fps): + DemoQtGui.instance.guiOnToggleRightEncoding(enabled, fps) + + @Slot(bool) + def toggleDepth(self, enabled): + DemoQtGui.instance.guiOnToggleDepth(enabled) + + @Slot(bool) + def toggleNN(self, enabled): + DemoQtGui.instance.guiOnToggleNN(enabled) + + @Slot(bool) + def toggleDisparity(self, enabled): + DemoQtGui.instance.guiOnToggleDisparity(enabled) + + +@QmlElement +class AIBridge(QObject): + @Slot(str) + def setCnnModel(self, name): + DemoQtGui.instance.guiOnAiSetupUpdate(cnn=name) + + @Slot(int) + def setShaves(self, value): + DemoQtGui.instance.guiOnAiSetupUpdate(shave=value) + + @Slot(str) + def setModelSource(self, value): + DemoQtGui.instance.guiOnAiSetupUpdate(source=value) + + @Slot(bool) + def setFullFov(self, value): + DemoQtGui.instance.guiOnAiSetupUpdate(fullFov=value) + + @Slot(bool) + def setSbb(self, value): + DemoQtGui.instance.guiOnAiSetupUpdate(sbb=value) + + @Slot(float) + def setSbbFactor(self, value): + if DemoQtGui.instance.writer is not None: + DemoQtGui.instance.guiOnAiSetupUpdate(sbbFactor=value) + + @Slot(str) + def setOvVersion(self, state): + DemoQtGui.instance.guiOnAiSetupUpdate(ov=state.replace("VERSION_", "")) + + @Slot(str) + def setCountLabel(self, state): + DemoQtGui.instance.guiOnAiSetupUpdate(countLabel=state) + + +@QmlElement +class PreviewBridge(QObject): + @Slot(str) + def changeSelected(self, state): + DemoQtGui.instance.guiOnPreviewChangeSelected(state) + + +@QmlElement +class DepthBridge(QObject): + @Slot(bool) + def toggleSubpixel(self, state): + DemoQtGui.instance.guiOnDepthSetupUpdate(subpixel=state) + + @Slot(bool) + def toggleExtendedDisparity(self, state): + DemoQtGui.instance.guiOnDepthSetupUpdate(extended=state) + + @Slot(bool) + def toggleLeftRightCheck(self, state): + DemoQtGui.instance.guiOnDepthConfigUpdate(lrc=state) + + @Slot(int) + def setDisparityConfidenceThreshold(self, value): + DemoQtGui.instance.guiOnDepthConfigUpdate(dct=value) + + @Slot(int) + def setLrcThreshold(self, value): + DemoQtGui.instance.guiOnDepthConfigUpdate(lrcThreshold=value) + + @Slot(int) + def setBilateralSigma(self, value): + DemoQtGui.instance.guiOnDepthConfigUpdate(sigma=value) + + @Slot(int, int) + def setDepthRange(self, valFrom, valTo): + DemoQtGui.instance.guiOnDepthSetupUpdate(depthFrom=valFrom, depthTo=valTo) + + @Slot(str) + def setMedianFilter(self, state): + value = getattr(dai.MedianFilter, state) + DemoQtGui.instance.guiOnDepthConfigUpdate(median=value) + + +@QmlElement +class ColorCamBridge(QObject): + name = "color" + + @Slot(int, int) + def setIsoExposure(self, iso, exposure): + if iso > 0 and exposure > 0: + DemoQtGui.instance.guiOnCameraConfigUpdate("color", sensitivity=iso, exposure=exposure) + + @Slot(int) + def setContrast(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("color", contrast=value) + + @Slot(int) + def setBrightness(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("color", brightness=value) + + @Slot(int) + def setSaturation(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("color", saturation=value) + + @Slot(int) + def setSharpness(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("color", sharpness=value) + + @Slot(int) + def setFps(self, value): + DemoQtGui.instance.guiOnCameraSetupUpdate("color", fps=value) + + @Slot(str) + def setResolution(self, state): + if state == "THE_1080_P": + DemoQtGui.instance.guiOnCameraSetupUpdate("color", resolution=1080) + elif state == "THE_4_K": + DemoQtGui.instance.guiOnCameraSetupUpdate("color", resolution=2160) + elif state == "THE_12_MP": + DemoQtGui.instance.guiOnCameraSetupUpdate("color", resolution=3040) + + +@QmlElement +class MonoCamBridge(QObject): + + @Slot(int, int) + def setIsoExposure(self, iso, exposure): + if iso > 0 and exposure > 0: + DemoQtGui.instance.guiOnCameraConfigUpdate("left", sensitivity=iso, exposure=exposure) + DemoQtGui.instance.guiOnCameraConfigUpdate("right", sensitivity=iso, exposure=exposure) + + @Slot(int) + def setContrast(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("left", contrast=value) + DemoQtGui.instance.guiOnCameraConfigUpdate("right", contrast=value) + + @Slot(int) + def setBrightness(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("left", brightness=value) + DemoQtGui.instance.guiOnCameraConfigUpdate("right", brightness=value) + + @Slot(int) + def setSaturation(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("left", saturation=value) + DemoQtGui.instance.guiOnCameraConfigUpdate("right", saturation=value) + + @Slot(int) + def setSharpness(self, value): + DemoQtGui.instance.guiOnCameraConfigUpdate("left", sharpness=value) + DemoQtGui.instance.guiOnCameraConfigUpdate("right", sharpness=value) + + @Slot(int) + def setFps(self, value): + DemoQtGui.instance.guiOnCameraSetupUpdate("left", fps=value) + DemoQtGui.instance.guiOnCameraSetupUpdate("right", fps=value) + + @Slot(str) + def setResolution(self, state): + if state == "THE_720_P": + DemoQtGui.instance.guiOnCameraSetupUpdate("left", resolution=720) + DemoQtGui.instance.guiOnCameraSetupUpdate("right", resolution=720) + elif state == "THE_800_P": + DemoQtGui.instance.guiOnCameraSetupUpdate("left", resolution=800) + DemoQtGui.instance.guiOnCameraSetupUpdate("right", resolution=800) + elif state == "THE_400_P": + DemoQtGui.instance.guiOnCameraSetupUpdate("left", resolution=400) + DemoQtGui.instance.guiOnCameraSetupUpdate("right", resolution=400) diff --git a/gui/views/AIProperties.qml b/gui/views/AIProperties.qml new file mode 100644 index 000000000..a821dbf79 --- /dev/null +++ b/gui/views/AIProperties.qml @@ -0,0 +1,320 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 + +ListView { + id: aiProperties + + Rectangle { + id: backgroundRectAI + color: "black" + z: 0 + width: parent.width + height: parent.height + + Text { + id: textAI + x: 79 + y: 8 + width: 322 + height: 30 + color: "#ffffff" + text: qsTr("AI Properties") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.family: "Courier" + } + + Rectangle { + id: aiRect + + Text { + id: text33 + x: 90 + y: 56 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("CNN model") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.weight: Font.Bold + font.family: "Courier" + } + + ComboBox { + id: comboBoxModels + x: 220 + y: 56 + width: 282 + height: 33 + model: modelChoices + onActivated: function(index) { + aiBridge.setCnnModel(model[index]) + } + } + + Text { + id: text34 + x: 90 + y: 134 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("Model source") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + ComboBox { + id: comboBox1 + x: 220 + y: 134 + width: 141 + height: 33 + model: modelSourceChoices + onActivated: function(index) { + aiBridge.setModelSource(model[index]) + } + } + + Text { + id: text36 + x: 90 + y: 95 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("SHAVEs") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + Slider { + id: lrcSlider + x: 221 + y: 95 + width: 261 + height: 33 + value: 6 + stepSize: 1 + onValueChanged: { + aiBridge.setShaves(value) + } + to: 16 + from: 1 + } + + Text { + id: text37 + x: 482 + y: 101 + width: 18 + height: 20 + color: "#ffffff" + text: lrcSlider.value + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rotation: 0 + } + + Text { + id: text43 + x: 403 + y: 134 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("Full FOV") + font.pixelSize: 12 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + CheckBox { + id: checkBoxFullFov + x: 369 + y: 137 + width: 37 + height: 23 + checked: true + onClicked: aiBridge.setFullFov(checkBoxFullFov.checked) + } + } + + CheckBox { + id: aiAdvancedSwitch + x: 167 + y: 228 + text: qsTr("Show advanced options") + font.family: "Courier" + autoExclusive: false + transformOrigin: Item.Center + font.pointSize: 21 + checked: false + font.preferShaping: false + font.kerning: false + } + + Rectangle { + id: aiAdvancedRect + + states: State { + name: "hidden"; when: !aiAdvancedSwitch.checked + PropertyChanges { target: aiAdvancedRect; opacity: 0 } + } + + Text { + id: text41 + x: 90 + y: 288 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("OpenVINO version") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + ComboBox { + id: comboBox2 + x: 220 + y: 288 + width: 170 + height: 33 + model: ovVersions + onActivated: function(index) { + aiBridge.setOvVersion(model[index]) + } + } + + Text { + id: text42 + x: 90 + y: 327 + width: 124 + height: 33 + color: "#ffffff" + text: qsTr("Label to count") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + ComboBox { + id: comboBox3 + x: 220 + y: 327 + width: 141 + height: 33 + model: countLabels + onActivated: function(index) { + aiBridge.setCountLabel(model[index]) + } + } + + Switch { + id: switch3 + x: 90 + y: 375 + width: 250 + height: 48 + text: qsTr("Spatial Bounding Boxes") + font.family: "Courier" + autoExclusive: false + transformOrigin: Item.Center + font.preferShaping: false + font.kerning: false + onToggled: { + aiBridge.setSbb(switch3.checked) + } + } + + Text { + id: text40 + x: 320 + y: 383 + width: 106 + height: 33 + color: "#ffffff" + text: qsTr("SBB factor") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + font.weight: Font.Bold + } + + Slider { + id: sbbFactorSlider + x: 423 + y: 383 + width: 96 + height: 33 + value: 0.3 + stepSize: 0.1 + to: 1 + from: 0.1 + onValueChanged: { + aiBridge.setSbbFactor(value) + } + } + + Text { + id: text39 + x: 527 + y: 389 + width: 18 + height: 20 + color: "#ffffff" + text: sbbFactorSlider.value.toFixed(1) + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rotation: 0 + } + } + + Switch { + id: switch5 + x: 359 + y: 0 + width: 167 + height: 38 + text: qsTr("Enabled") + checked: true + autoExclusive: false + font.family: "Courier" + font.kerning: false + transformOrigin: Item.Center + font.preferShaping: false + onToggled: { + appBridge.toggleNN(switch5.checked) + } + } + + } +} +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640}D{i:26} +} +##^##*/ diff --git a/gui/views/CameraPreview.qml b/gui/views/CameraPreview.qml new file mode 100644 index 000000000..0c85aac3d --- /dev/null +++ b/gui/views/CameraPreview.qml @@ -0,0 +1,65 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 + +import dai.gui 1.0 + +ListView { + id: cameraPreview + + Rectangle { + id: cameraPreviewRect + color: "black" + width: parent.width + height: 640 + + ComboBox { + id: comboBoxImage + x: 100 + y: 5 + width: 150 + height: 30 + model: previewChoices + onActivated: function(index) { + previewBridge.changeSelected(model[index]) + } + } + + ComboBox { + id: comboBoxDevices + x: 260 + y: 5 + width: 200 + height: 30 + model: deviceChoices + onActivated: function(index) { + appBridge.selectDevice(model[index]) + } + } + + Button { + x: 470 + y: 5 + height: 30 + width: 100 + text: "Reload" + onClicked: appBridge.reloadDevices() + } + + ImageWriter { + id: imageWriter + objectName: "writer" + x: 40 + y: 40 + width: parent.width - 80 + height: parent.height - 80 + } + } +} +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/gui/views/CameraProperties.qml b/gui/views/CameraProperties.qml new file mode 100644 index 000000000..f6f482520 --- /dev/null +++ b/gui/views/CameraProperties.qml @@ -0,0 +1,583 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 + +ListView { + id: cameraProperties + + Rectangle { + id: backgroundRect1 + color: "black" + width: parent.width + height: parent.height + + Text { + id: text10 + x: 79 + y: 8 + width: 433 + height: 30 + color: "#ffffff" + text: qsTr("Camera Properties") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.family: "Courier" + } + + Rectangle { + id: colorCamRect + + Text { + id: text11 + x: 0 + y: 44 + width: 197 + height: 30 + color: "#ffffff" + text: qsTr("Color") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.family: "Courier" + } + + Rectangle { + id: colorCamRectBasic + + ComboBox { + id: comboBox + x: 85 + y: 115 + width: 140 + height: 33 + model: colorResolutionChoices + onActivated: function(index) { + colorCamBridge.setResolution(model[index]) + } + } + + Text { + id: text32 + x: -6 + y: 84 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("FPS") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + TextField { + id: textField6 + x: 85 + y: 84 + width: 106 + height: 25 + text: "30" + bottomPadding: 5 + placeholderText: "FPS" + font.family: "Courier" + onEditingFinished: { + colorCamBridge.setFps(text) + } + validator: IntValidator {} + } + + Text { + id: text33 + x: -6 + y: 119 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Resolution") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + } + + Rectangle { + id: colorCamRectAdvanced + x: 30 + y: 180 + + states: State { + name: "hidden"; when: !advancedSwitch.checked + PropertyChanges { target: colorCamRectAdvanced; opacity: 0 } + } + + TextField { + id: textField + x: 85 + y: 80 + width: 106 + height: 25 + text: "" + bottomPadding: 5 + validator: IntValidator {} + placeholderText: "ISO" + font.family: "Courier" + onEditingFinished: { + colorCamBridge.setIsoExposure(text, textField1.text) + } + } + + Text { + id: text14 + x: -6 + y: 80 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("ISO") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + TextField { + id: textField1 + x: 85 + y: 111 + width: 106 + height: 25 + text: "" + bottomPadding: 5 + font.family: "Courier" + placeholderText: qsTr("Exposure") + validator: IntValidator {} + onEditingFinished: { + colorCamBridge.setIsoExposure(textField.text, text) + } + } + + + Text { + id: text15 + x: -6 + y: 111 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Exposure") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider2 + x: 85 + y: 142 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + colorCamBridge.setSaturation(value) + } + } + + Text { + id: text16 + x: -6 + y: 142 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Saturation") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider3 + x: 85 + y: 173 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + colorCamBridge.setContrast(value) + } + } + + Text { + id: text17 + x: -6 + y: 173 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Contrast") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider4 + x: 85 + y: 204 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + colorCamBridge.setBrightness(value) + } + } + + Text { + id: text18 + x: -6 + y: 204 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Brightness") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider5 + x: 85 + y: 235 + width: 106 + height: 25 + stepSize: 1 + to: 4 + from: 0 + value: 0 + onValueChanged: { + colorCamBridge.setSharpness(value) + } + } + + Text { + id: text19 + x: -6 + y: 235 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Sharpness") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + } + } + + Rectangle { + id: monoCamRectBasic + x: 309 + y: 0 + + ComboBox { + id: comboBox1 + x: 85 + y: 115 + width: 152 + height: 33 + model: monoResolutionChoices + onActivated: function(index) { + monoCamBridge.setResolution(model[index]) + } + } + + Text { + id: text34 + x: -6 + y: 84 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("FPS") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + TextField { + id: textField7 + x: 85 + y: 84 + width: 106 + height: 25 + text: "30" + bottomPadding: 5 + placeholderText: "FPS" + font.family: "Courier" + onEditingFinished: { + monoCamBridge.setFps(text) + } + validator: IntValidator {} + } + + Text { + id: text35 + x: -6 + y: 119 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Resolution") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + } + + Rectangle { + id: monoCamRect + + Text { + id: text13 + x: 330 + y: 44 + width: 181 + height: 30 + color: "#ffffff" + text: qsTr("Left + Right") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.family: "Courier" + } + + Rectangle { + id: monoCamRectAdvanced + x: 110 + y: 180 + + states: State { + name: "hidden"; when: !advancedSwitch.checked + PropertyChanges { target: monoCamRectAdvanced; opacity: 0 } + } + + Text { + id: text26 + x: 197 + y: 80 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("ISO") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + TextField { + id: textField4 + x: 288 + y: 80 + width: 106 + height: 25 + color: "#ddffffff" + text: "" + bottomPadding: 5 + placeholderText: "ISO" + font.family: "Courier" + validator: IntValidator {} + onEditingFinished: { + monoCamBridge.setIsoExposure(text, textField5.text) + } + } + + TextField { + id: textField5 + x: 288 + y: 111 + width: 106 + height: 25 + color: "#ddffffff" + text: "" + bottomPadding: 5 + placeholderText: qsTr("Exposure") + validator: IntValidator {} + onEditingFinished: { + monoCamBridge.setIsoExposure(textField4.text, text) + } + } + + Text { + id: text27 + x: 197 + y: 111 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Exposure") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider10 + x: 288 + y: 142 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + monoCamBridge.setSaturation(value) + } + } + + Text { + id: text28 + x: 197 + y: 142 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Saturation") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider11 + x: 288 + y: 173 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + monoCamBridge.setContrast(value) + } + } + + Text { + id: text29 + x: 197 + y: 173 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Contrast") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider12 + x: 288 + y: 204 + width: 106 + height: 25 + stepSize: 1 + to: 10 + from: -10 + value: 0 + onValueChanged: { + monoCamBridge.setBrightness(value) + } + } + + Text { + id: text30 + x: 197 + y: 204 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Brightness") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + Slider { + id: slider13 + x: 288 + y: 235 + width: 106 + height: 25 + stepSize: 1 + to: 4 + from: 0 + value: 0 + onValueChanged: { + monoCamBridge.setSharpness(value) + } + } + + Text { + id: text31 + x: 197 + y: 235 + width: 90 + height: 25 + color: "#ffffff" + text: qsTr("Sharpness") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + } + } + + CheckBox { + id: advancedSwitch + x: 132 + y: 216 + text: qsTr("Show advanced options") + font.pointSize: 21 + transformOrigin: Item.Center + autoExclusive: false + font.family: "Courier" + font.kerning: false + checked: false + font.preferShaping: false + } + } +} +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/gui/views/DepthProperties.qml b/gui/views/DepthProperties.qml new file mode 100644 index 000000000..2bd52bd63 --- /dev/null +++ b/gui/views/DepthProperties.qml @@ -0,0 +1,346 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 + +ListView { + id: depthProperties + delegate: Text { + anchors.leftMargin: 50 + font.pointSize: 15 + horizontalAlignment: Text.AlignHCenter + text: display + } + + Rectangle { + id: backgroundRect1 + color: "black" + width: parent.width + height: parent.height + + ComboBox { + id: comboBox + x: 0 + y: 102 + width: 195 + height: 33 + model: medianChoices + onActivated: function(index) { + depthBridge.setMedianFilter(model[index]) + } + } + + Slider { + id: dctSlider + x: 360 + y: 89 + width: 200 + height: 25 + snapMode: RangeSlider.NoSnap + stepSize: 1 + from: 0 + to: 255 + value: 240 + onValueChanged: { + depthBridge.setDisparityConfidenceThreshold(value) + } + } + + Text { + id: text2 + x: 0 + y: 71 + width: 195 + height: 25 + color: "#ffffff" + text: qsTr("Median filtering") + font.pixelSize: 18 + font.styleName: "Regular" + font.weight: Font.Medium + font.family: "Courier" + } + + Switch { + id: switch1 + x: 0 + y: 187 + text: qsTr("Left Right Check") + transformOrigin: Item.Center + font.preferShaping: false + font.kerning: false + font.family: "Courier" + autoExclusive: false + onToggled: { + depthBridge.toggleLeftRightCheck(switch1.checked) + } + } + + Switch { + id: switch5 + x: 328 + y: 0 + width: 167 + height: 38 + text: qsTr("Enabled") + autoExclusive: false + font.family: "Courier" + checked: true + font.kerning: false + transformOrigin: Item.Center + font.preferShaping: false + onToggled: { + appBridge.toggleDepth(switch5.checked) + } + } + + Switch { + enabled: false + id: switch2 + x: 0 + y: 233 + text: qsTr("Extended Disparity") + autoExclusive: false + font.kerning: false + font.family: "Courier" + font.preferShaping: false + transformOrigin: Item.Center + onToggled: { + depthBridge.toggleExtendedDisparity(switch2.checked) + } + } + + Switch { + id: switch3 + x: 0 + y: 141 + text: qsTr("Subpixel") + autoExclusive: false + font.kerning: false + transformOrigin: Item.Center + font.preferShaping: false + font.family: "Courier" + onToggled: { + depthBridge.toggleSubpixel(switch3.checked) + } + } + + Text { + id: text3 + x: 359 + y: 71 + width: 200 + height: 25 + color: "#ffffff" + text: qsTr("Confidence Threshold") + font.pixelSize: 18 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.weight: Font.Medium + font.family: "Courier" + } + + Slider { + id: sigmaSlider + x: 362 + y: 133 + width: 200 + height: 25 + stepSize: 1 + snapMode: RangeSlider.NoSnap + value: 0 + to: 255 + onValueChanged: { + depthBridge.setBilateralSigma(value) + } + } + + Text { + id: text5 + x: 566 + y: 95 + width: 17 + height: 25 + color: "#ffffff" + text: dctSlider.value + font.pixelSize: 12 + } + + Text { + id: text6 + x: 360 + y: 115 + width: 200 + height: 25 + color: "#ffffff" + text: qsTr("Bilateral Sigma") + font.pixelSize: 18 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.weight: Font.Medium + font.family: "Courier" + } + + Text { + id: text8 + x: 566 + y: 136 + width: 17 + height: 20 + color: "#ffffff" + text: sigmaSlider.value + font.pixelSize: 12 + rotation: 0 + } + + Text { + id: text9 + x: 362 + y: 158 + width: 200 + height: 25 + color: "#ffffff" + text: qsTr("Depth Range") + font.pixelSize: 18 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.weight: Font.Medium + font.family: "Courier" + } + + RangeSlider { + id: depthRangeSlider + x: 364 + y: 181 + width: 198 + height: 27 + snapMode: RangeSlider.NoSnap + stepSize: 100 + to: 10000 + focusPolicy: Qt.StrongFocus + second.value: 10000 + first.value: 0 + first.onMoved: { + depthBridge.setDepthRange(first.value, second.value) + } + second.onMoved: { + depthBridge.setDepthRange(first.value, second.value) + } + } + + Text { + id: text1 + x: 44 + y: 4 + width: 285 + height: 30 + color: "#ffffff" + text: qsTr("Depth Properties") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.family: "Courier" + } + + Text { + id: text32 + x: 566 + y: 185 + width: 17 + height: 20 + color: "#ffffff" + text: (depthRangeSlider.second.value / 1000).toFixed(1) + "m" + font.pixelSize: 12 + rotation: 0 + } + + Text { + id: text33 + x: 337 + y: 185 + width: 17 + height: 20 + color: "#ffffff" + text: (depthRangeSlider.first.value / 1000).toFixed(1) + "m" + font.pixelSize: 12 + rotation: 0 + } + + Text { + id: text10 + x: 360 + y: 214 + width: 200 + height: 25 + color: "#ffffff" + text: qsTr("LRC Threshold") + font.pixelSize: 18 + horizontalAlignment: Text.AlignHCenter + font.styleName: "Regular" + font.weight: Font.Medium + font.family: "Courier" + } + + Slider { + id: lrcSlider + x: 361 + y: 233 + width: 198 + height: 27 + stepSize: 1 + to: 10 + value: 10 + from: 0 + onValueChanged: { + depthBridge.setLrcThreshold(value) + } + } + + Text { + id: text34 + x: 566 + y: 233 + width: 17 + height: 20 + color: "#ffffff" + text: lrcSlider.value + font.pixelSize: 12 + rotation: 0 + } + + Text { + id: text35 + x: 337 + y: 233 + width: 17 + height: 20 + color: "#ffffff" + font.pixelSize: 12 + rotation: 0 + } + + Switch { + id: switch6 + x: 443 + y: 0 + width: 169 + height: 38 + text: qsTr("Use Disparity") + autoExclusive: false + font.family: "Courier" + font.kerning: false + transformOrigin: Item.Center + font.preferShaping: false + onToggled: { + appBridge.toggleDisparity(switch6.checked) + } + } + + + } +} +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640}D{i:7}D{i:24} +} +##^##*/ diff --git a/gui/views/MiscProperties.qml b/gui/views/MiscProperties.qml new file mode 100644 index 000000000..52531d06a --- /dev/null +++ b/gui/views/MiscProperties.qml @@ -0,0 +1,230 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 + +ListView { + id: miscProperties + delegate: Text { + anchors.leftMargin: 50 + font.pointSize: 15 + horizontalAlignment: Text.AlignHCenter + text: display + } + + Rectangle { + id: backgroundRect1 + color: "black" + width: parent.width + height: parent.height + + + Text { + id: text2 + x: 8 + y: 8 + width: 185 + height: 30 + color: "#ffffff" + text: qsTr("Recording") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.family: "Courier" + font.styleName: "Regular" + } + + TextField { + id: encColorFps + x: 110 + y: 44 + width: 83 + height: 27 + bottomPadding: 7 + validator: IntValidator {} + placeholderText: qsTr("FPS") + onEditingFinished: { + appBridge.toggleColorEncoding(encColorSwitch.checked, encColorFps.text) + } + } + + Switch { + id: encColorSwitch + x: 8 + y: 44 + width: 96 + height: 27 + text: qsTr("Color") + bottomPadding: 5 + onToggled: { + appBridge.toggleColorEncoding(encColorSwitch.checked, encColorFps.text) + } + } + + TextField { + id: encLeftFps + x: 110 + y: 77 + width: 83 + height: 27 + bottomPadding: 7 + validator: IntValidator {} + placeholderText: qsTr("FPS") + onEditingFinished: { + appBridge.toggleLeftEncoding(encLeftSwitch.checked, encLeftFps.text) + } + } + + Switch { + id: encLeftSwitch + x: 8 + y: 77 + width: 96 + height: 27 + text: qsTr("Left") + bottomPadding: 5 + onToggled: { + appBridge.toggleLeftEncoding(encLeftSwitch.checked, encLeftFps.text) + } + } + + TextField { + id: encRightFps + x: 110 + y: 110 + width: 83 + height: 27 + bottomPadding: 7 + validator: IntValidator {} + placeholderText: qsTr("FPS") + onEditingFinished: { + appBridge.toggleRightEncoding(encRightSwitch.checked, encRightFps.text) + } + } + + Switch { + id: encRightSwitch + x: 8 + y: 110 + width: 96 + height: 27 + text: qsTr("Right") + bottomPadding: 5 + onToggled: { + appBridge.toggleRightEncoding(encLeftSwitch.checked, encLeftFps.text) + } + } + + Text { + id: text3 + x: 8 + y: 203 + width: 185 + height: 30 + color: "#ffffff" + text: qsTr("Reporting") + font.pixelSize: 26 + horizontalAlignment: Text.AlignHCenter + font.family: "Courier" + font.styleName: "Regular" + } + + Switch { + id: tempSwitch + x: 8 + y: 239 + width: 185 + height: 27 + text: qsTr("Temperature") + bottomPadding: 5 + onToggled: { + appBridge.selectReportingOptions(tempSwitch.checked, cpuSwitch.checked, memSwitch.checked) + } + } + + Switch { + id: cpuSwitch + x: 8 + y: 272 + width: 185 + height: 27 + text: qsTr("CPU") + bottomPadding: 5 + onToggled: { + appBridge.selectReportingOptions(tempSwitch.checked, cpuSwitch.checked, memSwitch.checked) + } + } + + Switch { + id: memSwitch + x: 8 + y: 305 + width: 185 + height: 27 + text: qsTr("Memory") + bottomPadding: 5 + onToggled: { + appBridge.selectReportingOptions(tempSwitch.checked, cpuSwitch.checked, memSwitch.checked) + } + } + + TextField { + id: textField3 + x: 110 + y: 352 + width: 227 + height: 27 + bottomPadding: 7 + placeholderText: qsTr("/path/to/report.csv") + onEditingFinished: { + appBridge.selectReportingPath(text) + } + } + + Text { + id: text26 + x: 8 + y: 352 + width: 90 + height: 27 + color: "#ffffff" + text: qsTr("Destination") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + + TextField { + id: textField4 + x: 116 + y: 150 + width: 227 + height: 27 + bottomPadding: 7 + placeholderText: qsTr("/path/to/output/directory/") + onEditingFinished: { + appBridge.selectEncodingPath(text) + } + } + + Text { + id: text27 + x: 14 + y: 150 + width: 90 + height: 27 + color: "#ffffff" + text: qsTr("Destination") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Courier" + } + } +} +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640}D{i:11}D{i:12}D{i:13}D{i:14} +} +##^##*/ diff --git a/gui/views/root.qml b/gui/views/root.qml new file mode 100644 index 000000000..c8271a213 --- /dev/null +++ b/gui/views/root.qml @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Python. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.1 +import QtQuick.Controls.Material 2.1 +import Qt.labs.platform 1.1 + +import dai.gui 1.0 + +ApplicationWindow { + width: 1270 + height: 640 + Material.theme: Material.Dark + Material.accent: Material.Red + visible: true + + property var previewChoices + property var modelChoices + property var modelSourceChoices + property var ovVersions + property var countLabels + property var medianChoices + property var colorResolutionChoices + property var monoResolutionChoices + property var restartRequired + property var deviceChoices + + AppBridge { + id: appBridge + } + + DepthBridge { + id: depthBridge + } + ColorCamBridge { + id: colorCamBridge + } + MonoCamBridge { + id: monoCamBridge + } + PreviewBridge { + id: previewBridge + } + AIBridge { + id: aiBridge + } + + Rectangle { + id: root + x: 0 + y: 0 + width: parent.width + height: parent.height + color: "#000000" + enabled: true + + CameraPreview { + x: 0 + y: 0 + width: parent.width - 630 + height: parent.height + } + + TabBar { + id: bar + x: parent.width - 630 + y: 0 + height: 50 + width: 590 + + TabButton { + text: "AI" + } + + TabButton { + text: "Depth" + } + TabButton { + text: "Camera" + } + TabButton { + text: "Misc" + } + } + + StackLayout { + x: parent.width - 630 + y: 70 + width: 630 + currentIndex: bar.currentIndex + Item { + AIProperties {} + } + Item { + DepthProperties {} + } + Item { + CameraProperties {} + } + Item { + MiscProperties {} + } + } + + Button { + x: parent.width - 600 + y: 540 + enabled: restartRequired || false + height: 60 + width: 563 + text: "Apply and Restart" + onClicked: appBridge.applyAndRestart() + } + } +} diff --git a/launcher/windows/installer_win64.iss b/launcher/windows/installer_win64.iss index aeaa55ae0..9de67794f 100644 --- a/launcher/windows/installer_win64.iss +++ b/launcher/windows/installer_win64.iss @@ -150,6 +150,7 @@ begin Result := 1; end; +{* Handles uninstallation if previous DepthAI version is installed and shortcut creation *} procedure CurStepChanged(CurStep: TSetupStep); var ResultCode: Integer; @@ -159,12 +160,14 @@ begin if (IsUpgrade()) then begin UnInstallOldVersion(); - Log('Creating main shortcut'); - ExtractTemporaryFile('create_shortcut.ps1'); - ForceDirectories(ExpandConstant('{app}')); - FileCopy(ExpandConstant('{tmp}\create_shortcut.ps1'), ExpandConstant('{app}\create_shortcut.ps1'), False); - Exec('powershell.exe', ExpandConstant('-ExecutionPolicy Bypass -File {app}\create_shortcut.ps1'), ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode); end; + + Log('Creating main shortcut'); + ExtractTemporaryFile('create_shortcut.ps1'); + ForceDirectories(ExpandConstant('{app}')); + FileCopy(ExpandConstant('{tmp}\create_shortcut.ps1'), ExpandConstant('{app}\create_shortcut.ps1'), False); + Exec('powershell.exe', ExpandConstant('-ExecutionPolicy Bypass -File {app}\create_shortcut.ps1'), ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode); + end; end; diff --git a/requirements.txt b/requirements.txt index 6080ab2cc..46237a6a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ opencv-python==4.4.0.46 ; platform_machine == "armv6l" or platform_machine == "a opencv-contrib-python==4.4.0.46 ; platform_machine == "armv6l" or platform_machine == "armv7l" -e ./depthai_sdk --extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-snapshot-local/ -depthai==2.10.0.0.dev+7a0749a61597c086c5fd6e579618ae33accec8df +depthai==2.11.1.0.dev+dfd52ac01c3b7514e85cb894b9d5381e999859df +PySide6==6.2.0 \ No newline at end of file